tvOS
TVML
appleTV
TVJS

Swiftを使わずにTVMLだけでアプリ開発 1

この度株式会社ネクステージからAppleTV観劇三昧をリリースするにあたり、メインで開発を行いました。藤島です。
普段は、PHPをメインにWEBのミドルエンジニアっぽいことをしています。
今回のアプリ開発で、最も重要な点は、Appleのアプリ開発で標準的に使用されるSwiftを使わず、HTMLの様にアプリが作れるTVMLと、TVMLKitのライブラリを使用できるjs、TVJSを使ってアプリ開発を行ったことです。

そもそも私、アプリって作ったことがなくSwiftも、触れたことなし。話をもらった時に、Appleさんから、TVMLを使うことで、jsとPHPだけで開発できますよ。とのことで私に回ってきた案件でした。

しかし、このTVMLがなかなか曲者、何が曲者って新しい規格でしかもテレビデバイスの専用言語ほとんど国内に知見がないのです。もちろん、書籍なんてあるわけもなく、英語の公式マニュアルと、英語の質問サイトを行ったり来たり。

でもこれすごいですよ。仕様の勉強、専用APIの作成、UIまで全部合わせて30日程度(作業日数)でアプリがリリースできちゃいました。

今回は、国内にまだまだ情報の少ないTVML、アプリ開発にあたっての苦労話と注意点を中心にTVML開発環境のスタートの仕方を書けたらと思います。

参考サイト

今回の開発にあたり、大変お世話になった公式マニュアルや、各記事をまとめておきます。
Apple TVのtvOSについてざっくりとまとめた! | Qiita Apple TV(第4世代)が発売にされるにあたってマニュアルをまとめている記事
Beginning tvOS Development with TVML Tutorial | raywenderlich.com TVMLアプリケーション開発の入門説明を書いたサイト
tvOS プログラミングガイド | Apple
ヒューマンインターフェースガイドライン | Apple
TVMLリファレンス | Apple
TVJSリファレンス | Apple

テスト環境

以下の環境でテスト、ビルドを行いました。

Xcode 8.3.2
Apple TV (第 4 世代) A1625
PHP 7.0

Xcodeでやること

File > new Project > tvOS > TVML Application を選択。
ファイルが作られたら、AppDelegate.swiftの
static let tvBaseURL = "http://localhost:9001/“
の部分をTVMLを置いておくWEBページに指定。(もちろんローカルでテストする場合はローカルのページを指定。)

localhostがセキュア通信に対応していないと思うので、localhostの通信の制限を解除する。(通常ではHTTPSでなければ通信できない。)
ちょうどいい参考ページがあるので、以下参照のこと。
http://qiita.com/akatsuki174/items/176886ac9f695e2f3d29

あとは、アプリアイコンを設定するだけ。
アイコン等の設定については、iOSなどと変わらないと思うので、割愛

static let tvBaseURL = "http://localhost:9001/“
の1行下にある
static let tvBootURL = "\(AppDelegate.tvBaseURL)application.js”
のapplication.jsがアプリが起動した時に読み込まれるjsファイル。
これが、アプリの動作部分(コントローラ)になる。

TVMLアプリは、アプリ自体には何も保存されていなく、画面遷移のたびに全てWEBサーバーから、XML(TVML)をダウンロードしてきて、jsを通して変換表示するという仕組み。本来Swiftでやらなくてはいけないものは、jsのライブラリからアクセスできるようになっている。
Apple TVがオフラインだと、何も表示でない。オフラインで使用したい場合はアプリのディレクトリにサーバーに置くためのファイルを入れてビルドすれば、オフラインでも使える。

TVMLについて

リファレンスはこちら。
TVML公式リファレンス:https://developer.apple.com/library/content/documentation/LanguagesUtilities/Conceptual/ATV_Template_Guide/index.html#//apple_ref/doc/uid/TP40015064
Templateから、使用できるテンプレートが表示できるので、コピーしてきて、必要な文字情報を変えるだけ。という感じで使用できる。
作りたいページに近いテンプレートを選んで適当に変更を加えるしかない。

一番簡単な例alertTemplateだとこんな感じ(公式から引用・変更)

<?xml version="1.0" encoding="UTF-8" ?>
<document>
   <herd>
      <style></style>
   </herd>
   <alertTemplate>
      <background>
        <img src="imageURL" />
      </background>
      <title>タイトル</title>
      <description>詳細</description>
      <button onSelect=“bt1()”>
         <text>ボタン1</text>
      </button>
      <button onSelect=“bt2()”>
         <text>ボタン2</text>
      </button>
   </alertTemplate>
</document>

公式の一例には、記述がない注意点

エンコードの設定

1行目の<?xml version="1.0" encoding="UTF-8" ?>はエンコードを指定するために必要。UTF8が標準なのでなくても文字化けはしなそう。

スタイルを記述する位置

   <herd>
      <style></style>
   </herd>

については、ページごとのスタイル指定する際に必要。
タグの中にstyle属性を使って記述することはできない。idも使えない。
スタイルについてはCSSと記述方法は同じだけれど、CSSが使えるわけではないので注意が必要。
公式リファレンスのStyleの項目からそのStyleがあるか確認して使用する。
公式リファレンスに指定できるタグのリストがあるけれどあてにならないので使って見るしかない。

背景の設定

<background>
  <img src="imageURL" />
</background>

ほぼ全てのテンプレートで背景画像を指定することができる。
テンプレートに背景カラーを指定することができるテンプレートもあるが、基準がわからないけれどどうも全てではない。
単色でも、ベタ塗りの背景画像を作って、背景画像を指定するしかない。

クリック

<button> の中にある onSelect=はAppleTVのリモコンをクリックした時のJsのイベント。onClickと同じように使えばいい。
もちろん、js側からEventListenerからでも制御できる。

application.js(TVJS)について

リファレンスはこちら。
TVJSの公式リファレンス:https://developer.apple.com/documentation/tvmljs

application.jsに書くこと。

アプリ起動時にApp.onLaunchのファンクションが呼び出される。
optionsのオブジェクトから、AppDelegate.swiftに書いてある変数を受け取ることができる。

application.js
var baseURL;
App.onLaunch = function(options) {
    // アプリ起動時にここから実行される。
    baseURL = options.tvBaseURL;
}

画面遷移に使うのは、NavigationDocumentクラス
TVML、テキスト、Jsonなどコンテンツを読み込むためには、XMLHttpRequestクラス(JQueryのAjaxと同じような動きをする。)
この2つだけ理解していればとりあえず、OK

標準のDocument Object Moduleクラスが組み込まれているらしいけれど、他で見たこともないような使い方をすることが結構あるので、ほぼ呪文みたいなものだと思っていいきがする。

起動したら、メッセージを表示する、アプリを作ると

application.js
App.onLaunch = function(options) {
    baseURL = options.tvBaseURL; //サーバーURL
    //表示するTVML
    var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
        <document>
          <alertTemplate>
            <title>Halo World</title>
            <description>こんにちは世界</description>
            <button onselect=“halo()”>
              <text>こんにちは</text>
            </button>
          </alertTemplate>
        </document>`;
    //ドキュメントオブジェクトに変換
        var parser = new DOMParser();
        var alertDoc = parser.parseFromString(alertString, "application/xml");
        //ドキュメントをプッシュ
    navigationDocument.pushDocument(alertDoc);
}

こん感じで書けば、表示できる。
string型になってる変数をDOMDocumentオブジェクトに変換しないとViewにプッシュできないので注意。
TVMLをファイルから読み込む場合は、XMLHttpRequestを使ってダウンロードしてくる。

application.js
App.onLaunch = function(options) {
    baseURL = options.tvBaseURL; //サーバーURL
    var path = baseURL + halo.xml; //ダウンロードするURL絶対パス
    var doc = getDocumentContents(path);
    navigationDocument.pushDocument(doc); //ドキュメントをプッシュ
}
//ドキュメントロード用ファンクション
function getDocumentContents(path) {
    var templateXHR = new XMLHttpRequest();
    templateXHR.responseType = "document"; //ファイルタイプをドキュメントでダウンロード
    templateXHR.open("GET", path, 0); //引数 メソッド,URL,同期OR非同期
    templateXHR.send(); //リクエスト開始
    return templateXHR.responseXML;
}
halo.xml
<?xml version="1.0" encoding="UTF-8" ?>
<document>
  <alertTemplate>
    <title>Halo World</title>
    <description>こんにちは世界</description>
    <button onselect=“halo()”>
      <text>こんにちは</text>
    </button>
  </alertTemplate>
</document>

こんな感じ。
今度は、string型から、DOMDocumentに変換する部分は記述の必要はない。どうも、templateXHR.responseTypeをdocumentに指定すると、勝手にDOMDocumentオブジェクトに変化するらしい。他にタイプがtext,jsonがあるのを確認している。

templateXHR.open("GET", path, 0);についてですが、最後の0は同期通信か非同期通信かを指定している引数。0が同期通信、1が非同期の通信。引数は任意で、設定しなければ、非同期の通信になる。
非同期の通信の場合、
send()の直後に

templateXHR.addEventListener("load", function() {
    }
, false);

を書いて、functionの中に、動作を書けば、いいみたいです。
読み込み中の画面に進行状況を表示したい場合以外は同期通信で十分なきがする。

実は、画面遷移については、TVMLでは一切制御でない。画面繊維のイメージは
Jsイベントを発生させ、ファンクションを呼び出す > TVMLをダウンロード OR 内部に記述 > 変換してViewにプッシュする。
ここまでやって初めて、ページが移動できる。
この調子で、どんどんテンプレートからコピーして、作れば、アプリが完成する。

デバッグについて

こんな感じでお手軽に書くことができるTVMLですが、アプリ内にデータがないので、そもそもデバッグってどうやってやるの?jsのデバッグってコンソールがないけどと思うかと思います。
シュミュレーターからアプリを起動、もしくは、テスト用アプリをAppleTVにインストールしたMacをAppleTVにつないで、アプリを起動。
そしたら、Safariを起動して、開発のタグから、SimulatorもしくはApple TVが出てくるので、そこから、アプリのドメイン名を選択すると、おなじみのDOMコンソールが出てきます。
コンソールのへの出力方法は標準のJSと同じで、console.logで見ることができます。

終わりに

このような感じで、テンプレートを改造して、とりあえず静的ページを作って、そのあと、ユーザーや表示した時によってPHPでTVMLの出力を行いました。
まだまだ癖のあるTVMLですが、少しずつ今回の開発を通じた解説を書けたらと思います。