できること
MESHのSDKでThetaVと連携できるカスタムタグを作成しました。下記の3つの機能がMESH上のタグと組み合わせて利用する事が出来ます。
① 360 Live Photos:指定された時間だけ動画が撮影されます。
② 写真撮影:写真を撮影します。
③ 動画撮影:動画の開始・停止をコントロールできます。
360 Live Photos機能は、iPhoneのLive Photos機能が好きで良く使うので、あえて動画撮影機能と分けて実装しました。
きっかけ
ThetaVで撮影をしていると本体ボタンを押している手や、スマホ越しにリモート撮影している自分が映ってしまうのがどうしても嫌だったので、色んなトリガーを設定できて、その場で調整が手軽にできるMESHと繋げてその問題を解決しようと思い、今回MESH用のThetaV連携タグを作成しました。
実装方法
とりあえず使ってみたい方は、下記のJSON DATAをコピーしてimportしてご利用ください。
https://gloryroad0713.github.io/mesh.github.io/mesh_thetav.html
360 Live Photos
360 Live Photos機能は、大まかに下記の流れで処理を実行しています。
① 撮影前状態の取得
② ビデオモード状態か否かの確認
③ もし写真モードだったら、ビデオモードに切り替える
④ 動画の撮影開始
⑤ 指定時間経過後、動画の撮影停止
//Theta Endpoint URL の設定(デフォルトは192.168.1.1:80)
var endPointURL = "http://192.168.1.1:80"
wait().then((v) => {
log("wait " + v + " millisecond");
closeCaptureSession(endPointURL) ;
callbackSuccess( {
resultType : "continue"
} );
});
function sleep() {
return new Promise(resolve => setTimeout(resolve,properties.millisecond));
}
async function wait() {
checkState(endPointURL);
//非同期処理が完了するまで待機
await sleep();
return properties.millisecond.toString();
}
function checkState(_endPointURL) {
ajax( {
url : _endPointURL + "/osc/state",
type : "post",
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
checkCaptureMode(endPointURL);
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log(XMLHttpRequest + " | " + textStatus + " | " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
});
}
//Theta Vの撮影モードがvideoモードになっている確認
function checkCaptureMode(endPointURL) {
var getOptionsJSONData = {
name: "camera.getOptions",
parameters: {
optionNames: [
"captureMode"
]
}
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( getOptionsJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
var json_data = JSON.stringify(contents);
var json_text = JSON.parse(json_data)
log("checkCaptureMode = " + json_data);
var mode = json_text["results"]["options"]["captureMode"];
//写真モードだったら、動画モードに切り替える
if(mode == "image"){
setCaptureMode(endPointURL, "video");
}
else{
startCaptureSession(endPointURL);
}
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//Theta Vの撮影モードを変更する
function setCaptureMode(endPointURL, _mode) {
var getOptionsJSONData = {
name: "camera.setOptions",
parameters: {
options: {
captureMode: _mode
}
}
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( getOptionsJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画モードに変更");
startCaptureSession(endPointURL);
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//Theta の動画の撮影をスタートする
function startCaptureSession(endPointURL) {
var takePictureJSONData = {
name: "camera.startCapture",
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( takePictureJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画撮影スタート");
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//動画の撮影を終了する
function closeCaptureSession(endPointURL) {
var closeSessionJSONData = {
name: "camera.stopCapture",
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( closeSessionJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画撮影ストップ");
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
return {
resultType : "pause"
};
写真撮影
写真撮影機能は、大まかに下記の流れで処理を実行しています。
① 撮影前状態の取得
② 写真モード状態か否かの確認
③ もしビデオモードだったら、写真モードに切り替える
④ 写真の撮影開始
//Theta Endpoint URL の設定(デフォルトは192.168.1.1:80)
var endPointURL = "http://192.168.1.1:80"
wait().then((v) => {
log("wait " + v + " millisecond");
closeCaptureSession(endPointURL) ;
callbackSuccess( {
resultType : "continue"
} );
});
function sleep() {
return new Promise(resolve => setTimeout(resolve,properties.millisecond));
}
async function wait() {
checkState(endPointURL);
//非同期処理が完了するまで待機
await sleep();
return properties.millisecond.toString();
}
function checkState(_endPointURL) {
ajax( {
url : _endPointURL + "/osc/state",
type : "post",
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
checkCaptureMode(endPointURL);
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log(XMLHttpRequest + " | " + textStatus + " | " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
});
}
//Theta Vの撮影モードがvideoモードになっている確認
function checkCaptureMode(endPointURL) {
var getOptionsJSONData = {
name: "camera.getOptions",
parameters: {
optionNames: [
"captureMode"
]
}
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( getOptionsJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
var json_data = JSON.stringify(contents);
var json_text = JSON.parse(json_data)
log("checkCaptureMode = " + json_data);
var mode = json_text["results"]["options"]["captureMode"];
//写真モードだったら、動画モードに切り替える
if(mode == "image"){
setCaptureMode(endPointURL, "video");
}
else{
startCaptureSession(endPointURL);
}
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//Theta Vの撮影モードを変更する
function setCaptureMode(endPointURL, _mode) {
var getOptionsJSONData = {
name: "camera.setOptions",
parameters: {
options: {
captureMode: _mode
}
}
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( getOptionsJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画モードに変更");
startCaptureSession(endPointURL);
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//Theta の動画の撮影をスタートする
function startCaptureSession(endPointURL) {
var takePictureJSONData = {
name: "camera.startCapture",
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( takePictureJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画撮影スタート");
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//動画の撮影を終了する
function closeCaptureSession(endPointURL) {
var closeSessionJSONData = {
name: "camera.stopCapture",
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( closeSessionJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画撮影ストップ");
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
return {
resultType : "pause"
};
動画撮影
動画撮影機能は、大まかに下記の流れで処理を実行しています。
① 撮影前状態の取得
② ビデオモード状態か否かの確認
③ もし写真モードだったら、ビデオモードに切り替える
④ ビデオの撮影開始/停止
return {
runtimeValues : {
inputIndex : 0,
outputIndex:0
},
resultType : "continue"
};
//Theta Endpoint URL の設定(デフォルトは192.168.1.1:80)
var endPointURL = "http://192.168.1.1:80"
checkState(endPointURL);
function checkState(_endPointURL) {
ajax( {
url : _endPointURL + "/osc/state",
type : "post",
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
checkCaptureMode(endPointURL);
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log(XMLHttpRequest + " | " + textStatus + " | " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
});
}
//Theta Vの撮影モードがvideoモードになっている確認
function checkCaptureMode(endPointURL) {
var getOptionsJSONData = {
name: "camera.getOptions",
parameters: {
optionNames: [
"captureMode"
]
}
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( getOptionsJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
var json_data = JSON.stringify(contents);
var json_text = JSON.parse(json_data)
log("checkCaptureMode = " + json_data);
var mode = json_text["results"]["options"]["captureMode"];
if(runtimeValues.inputIndex == 0){
//写真モードだったら、動画モードに切り替える
if(mode == "image"){
setCaptureMode(endPointURL, "video");
}
else{
startCaptureSession(endPointURL);
}
}
else if(runtimeValues.inputIndex == 1){
closeCaptureSession(endPointURL);
}
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//Theta Vの撮影モードを変更する
function setCaptureMode(endPointURL, _mode) {
var getOptionsJSONData = {
name: "camera.setOptions",
parameters: {
options: {
captureMode: _mode
}
}
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( getOptionsJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画モードに変更");
startCaptureSession(endPointURL);
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//Theta の動画の撮影をスタートする
function startCaptureSession(endPointURL) {
var takePictureJSONData = {
name: "camera.startCapture",
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( takePictureJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画撮影スタート");
runtimeValues.outputIndex = 0;
callbackSuccess({
resultType: "continue"
});
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
//動画の撮影を終了する
function closeCaptureSession(endPointURL) {
var closeSessionJSONData = {
name: "camera.stopCapture",
}
ajax( {
url : endPointURL + "/osc/commands/execute",
type : "post",
data : JSON.stringify( closeSessionJSONData),
contentType: "application/json",
dataType: "json",
timeout : 5000,
success : function (contents) {
log("動画撮影ストップ");
runtimeValues.outputIndex = 1;
callbackSuccess({
resultType: "continue"
});
},
error : function (XMLHttpRequest, textStatus, errorThrown) {
log("Error:" + textStatus + " " + errorThrown);
callbackSuccess({
resultType: "stop"
});
}
} );
}
return {
resultType : "pause"
};
return {
indexes : [ runtimeValues.outputIndex ],
resultType : "continue"
};
使ってみて
ボタンタグでリモート撮影や、マイクタグで盛り上がった時に自動撮影、明るさタグで手をかざして数秒後に撮影など、色々なトリガーを条件に撮影ができるのでより自然な360度写真/動画が撮影できた。ただ、トリガーが重なり命令が重なるとThetaVが上手くコントロールできず誤動作することがあったので、以前作った出力制御タグを活用しました。