Sencha Ext JS でアプリを作成する

このドキュメントは、Sencha Ext JS を使用した Chrome アプリの構築を開始することを目的としています。 説明します。この目標を達成するために、Sencha が構築したメディア プレーヤー アプリについて詳しく説明します。参照元 コードAPI ドキュメントは GitHub から入手できます。

このアプリは、ユーザーが利用可能なメディア サーバーを検出します。これには、PC に接続されているメディア デバイス、 ネットワーク上でメディアを管理するソフトウェアです。メディアのブラウジング、ネットワーク経由での再生、保存を行える オフラインです。

Sencha Ext JS を使用してメディア プレーヤー アプリを構築するには、次のことを行う必要があります。

  • マニフェスト manifest.json を作成します。
  • イベントページ background.js を作成します。
  • サンドボックス アプリのロジック。
  • Chrome アプリとサンドボックス化されたファイルの間で通信を行います。
  • メディア サーバーを検出します。
  • メディアを探して再生。
  • メディアをオフラインに一時保存します。

マニフェストを作成

すべての Chrome アプリで必要なマニフェスト ファイルには、Chrome の起動に必要な情報が含まれています 。マニフェストに示されているように、メディア プレーヤー アプリは「offline_enabled」です。メディアアセットは 接続性に関係なく、ローカルに保存し、アクセス、再生します。

「サンドボックス」フィールドを使用して、アプリのメインロジックを一意のオリジンでサンドボックス化します。すべてサンドボックス化 のコンテンツは Chrome アプリのコンテンツ セキュリティ ポリシーの対象外ですが、 Chrome App API。マニフェストには "socket" も含まれており、権限メディア プレーヤー アプリは socket API。ネットワーク経由でメディア サーバーに接続します。

{
    "name": "Video Player",
    "description": "Features network media discovery and playlist management",
    "version": "1.0.0",
    "manifest_version": 2,
    "offline_enabled": true,
    "app": {
        "background": {
            "scripts": [
                "background.js"
            ]
        }
    },
    ...

    "sandbox": {
        "pages": ["sandbox.html"]
    },
    "permissions": [
        "experimental",
        "http://*/*",
        "unlimitedStorage",
        {
            "socket": [
                "tcp-connect",
                "udp-send-to",
                "udp-bind"
            ]
        }
    ]
}

イベント作成ページ

すべての Chrome アプリでこのアプリを起動するには、background.js が必要です。メディア プレーヤーのメインページ index.html は、指定されたディメンションのウィンドウで開きます。

chrome.app.runtime.onLaunched.addListener(function(launchData) {
    var opt = {
        width: 1000,
        height: 700
    };

    chrome.app.window.create('index.html', opt, function (win) {
        win.launchData = launchData;
    });

});

サンドボックス アプリのロジック

Chrome アプリは、厳格なコンテンツ セキュリティ ポリシーが適用される管理された環境で実行されます。 (CSP)をご覧ください。メディア プレーヤー アプリで Ext JS コンポーネントをレンダリングするには、より高い権限が必要です。宛先 CSP に準拠してアプリロジックを実行すると、アプリのメインページ(index.html)で iframe が作成され、 サンドボックス環境として機能します。

<iframe id="sandbox-frame" sandbox="allow-scripts" src="sandbox.html"></iframe>

iframe は、Ext JS に必要なファイルを含む sandbox.html を参照します。 アプリケーション:

<html>
<head>
    <link rel="stylesheet" type="text/css" href="resources/css/app.css" />'
    <script src="sdk/ext-all-dev.js"></script>'
    <script src="lib/ext/data/PostMessage.js"></script>'
    <script src="lib/ChromeProxy.js"></script>'
    <script src="app.js"></script>
</head>
<body></body>
</html>

app.js スクリプトはすべての Ext JS コードを実行し、メディア プレーヤー ビューをレンダリングします。これが スクリプトはサンドボックス化されているため、Chrome App API に直接アクセスすることはできません。app.js 間の通信 サンドボックス化されていないファイルは、HTML5 Post Message API を使用して作成されます。

ファイル間で通信する

メディア プレーヤー アプリが Chrome アプリの API(ネットワークへのメディアの照会など)にアクセスするには、 app.js がメッセージを index.js に送信します。サンドボックス化された app.js とは異なり、index.js は Chrome アプリ API に直接アクセスできます。

index.js が iframe を作成します。

var iframe = document.getElementById('sandbox-frame');

iframeWindow = iframe.contentWindow;

また、サンドボックス化されたファイルからのメッセージをリッスンします。

window.addEventListener('message', function(e) {
    var data= e.data,
        key = data.key;

    console.log('[index.js] Post Message received with key ' + key);

    switch (key) {
        case 'extension-baseurl':
            extensionBaseUrl(data);
            break;

        case 'upnp-discover':
            upnpDiscover(data);
            break;

        case 'upnp-browse':
            upnpBrowse(data);
            break;

        case 'play-media':
            playMedia(data);
            break;

        case 'download-media':
            downloadMedia(data);
            break;

        case 'cancel-download':
            cancelDownload(data);
            break;

        default:
            console.log('[index.js] unidentified key for Post Message: "' + key + '"');
    }
}, false);

次の例では、app.jsindex.js にメッセージを送信し、鍵をリクエストします。 'extension-baseurl':

Ext.data.PostMessage.request({
    key: 'extension-baseurl',
    success: function(data) {
        //...
    }
});

index.js がリクエストを受信して結果を割り当て、ベース URL を返して応答します。

function extensionBaseUrl(data) {
    data.result = chrome.extension.getURL('/');
    iframeWindow.postMessage(data, '*');
}

メディア サーバーを検出する

メディア サーバーの検出には多くの作業が伴います。調査ワークフローの概要は次のとおりです 使用可能なメディア サーバーを検索するユーザーの操作によって開始されます。MediaServer コントローラ index.js さんにメッセージを投稿しました。index.js はこのメッセージをリッスンし、メッセージを受信すると、 Upnp.js

Upnp library は、Chrome アプリの Socket API を使用して、メディア プレーヤー アプリを任意のネットワークに接続します。 メディア サーバーを検出し、そのメディア サーバーからメディア データを受信します。Upnp.js では次も使用しています: soapclient.js: メディア サーバーのデータを解析します。このセクションの残りの部分では、 詳しく説明します。

メッセージを投稿

ユーザーがメディア プレーヤー アプリの中央にあるメディア サーバー ボタンをクリックすると、MediaServers.js discoverServers() を呼び出します。この関数は、まず未処理の検出リクエストの有無を確認し、 true の場合、それらを中止して新しいリクエストを開始できるようにします。次に、コントローラがメッセージを キー upnp-discovery と 2 つのコールバック リスナーを持つ index.js:

me.activeDiscoverRequest = Ext.data.PostMessage.request({
    key: 'upnp-discover',
    success: function(data) {
        var items = [];
        delete me.activeDiscoverRequest;

        if (serversGraph.isDestroyed) {
            return;
        }

        mainBtn.isLoading = false;
        mainBtn.removeCls('pop-in');
        mainBtn.setIconCls('ico-server');
        mainBtn.setText('Media Servers');

        //add servers
        Ext.each(data, function(server) {
            var icon,
                urlBase = server.urlBase;

            if (urlBase) {
                if (urlBase.substr(urlBase.length-1, 1) === '/'){
                        urlBase = urlBase.substr(0, urlBase.length-1);
                }
            }

            if (server.icons && server.icons.length) {
                if (server.icons[1]) {
                    icon = server.icons[1].url;
                }
                else {
                    icon = server.icons[0].url;
                }

                icon = urlBase + icon;
            }

            items.push({
                itemId: server.id,
                text: server.friendlyName,
                icon: icon,
                data: server
            });
        });

        ...
    },
    failure: function() {
        delete me.activeDiscoverRequest;

        if (serversGraph.isDestroyed) {
            return;
        }

        mainBtn.isLoading = false;
        mainBtn.removeCls('pop-in');
        mainBtn.setIconCls('ico-error');
        mainBtn.setText('Error...click to retry');
    }
});

upnpDiscover() を呼び出す

index.js が「upnp-discover」をリッスンするapp.js さんからメッセージが届き、 upnpDiscover()。メディア サーバーが検出されると、index.js はメディア サーバーのドメインを抽出します サーバーをローカルに保存し、メディア サーバーのデータをフォーマットして、 MediaServer コントローラ。

メディア サーバーデータを解析する

Upnp.js は、新しいメディア サーバーを検出すると、デバイスの説明を取得して、 メディアサーバーのデータを参照および解析する Soaprequestsoapclient.js はメディア要素を解析します。 ドキュメントに変換します。

メディア サーバーに接続

Upnp.js は、検出されたメディア サーバーに接続し、Chrome アプリソケットを使用してメディアデータを受信します。 API:

socket.create("udp", {}, function(info) {
    var socketId = info.socketId;

    //bind locally
    socket.bind(socketId, "0.0.0.0", 0, function(info) {

        //pack upnp message
        var message = String.toBuffer(UPNP_MESSAGE);

        //broadcast to upnp
        socket.sendTo(socketId, message, UPNP_ADDRESS, UPNP_PORT, function(info) {

            // Wait 1 second
            setTimeout(function() {

                //receive
                socket.recvFrom(socketId, function(info) {

                    //unpack message
                    var data        = String.fromBuffer(info.data),
                        servers     = [],
                        locationReg = /^location:/i;

                    //extract location info
                    if (data) {
                        data = data.split("\r\n");

                        data.forEach(function(value) {
                            if (locationReg.test(value)){
                                servers.push(value.replace(locationReg, "").trim());
                            }
                        });
                    }

                    //success
                    callback(servers);
                });

            }, 1000);
        });
    });
});

メディアを探して再生する

MediaExplorer コントローラは、メディア サーバー フォルダ内のすべてのメディア ファイルを一覧表示し、 メディア プレーヤー アプリ ウィンドウのパンくずリスト ナビゲーションを更新します。ユーザーが メディア ファイルを選択すると、コントローラは「play-media」で index.js にメッセージを投稿します。key:

onFileDblClick: function(explorer, record) {
    var serverPanel, node,
        type    = record.get('type'),
        url     = record.get('url'),
        name    = record.get('name'),
        serverId= record.get('serverId');

    if (type === 'audio' || type === 'video') {
        Ext.data.PostMessage.request({
            key     : 'play-media',
            params  : {
                url: url,
                name: name,
                type: type
            }
        });
    }
},

index.js がこの投稿メッセージをリッスンし、playMedia() を呼び出して応答します。

function playMedia(data) {
    var type        = data.params.type,
        url         = data.params.url,
        playerCt    = document.getElementById('player-ct'),
        audioBody   = document.getElementById('audio-body'),
        videoBody   = document.getElementById('video-body'),
        mediaEl     = playerCt.getElementsByTagName(type)[0],
        mediaBody   = type === 'video' ? videoBody : audioBody,
        isLocal     = false;

    //save data
    filePlaying = {
        url : url,
        type: type,
        name: data.params.name
    };

    //hide body els
    audioBody.style.display = 'none';
    videoBody.style.display = 'none';

    var animEnd = function(e) {

        //show body el
        mediaBody.style.display = '';

        //play media
        mediaEl.play();

        //clear listeners
        playerCt.removeEventListener( 'transitionend', animEnd, false );
        animEnd = null;
    };

    //load media
    mediaEl.src = url;
    mediaEl.load();

    //animate in player
    playerCt.addEventListener( 'transitionend', animEnd, false );
    playerCt.style.transform = "translateY(0)";

    //reply postmessage
    data.result = true;
    sendMessage(data);
}

メディアをオフラインに保存する

メディアをオフラインに保存するための処理のほとんどは、filer.js ライブラリを使用して行われます。続きを読む このライブラリについては、filer.js の概要をご覧ください。

このプロセスは、ユーザーが 1 つ以上のファイルを選択して [オフラインにする] を開始すると開始されます。できます。 MediaExplorer コントローラは、キー「download-media」を含むメッセージを index.js に送信します。 index.js はこのメッセージをリッスンし、downloadMedia() 関数を呼び出して ダウンロード プロセス:

function downloadMedia(data) {
        DownloadProcess.run(data.params.files, function() {
            data.result = true;
            sendMessage(data);
        });
    }

DownloadProcess ユーティリティ メソッドは、メディア サーバーからデータを取得する xhr リクエストを作成し、 完了ステータスを待ちます。これにより、受信したコンテンツをチェックする onload コールバックが開始します。 filer.js 関数を使用してデータをローカルに保存します。

filer.write(
    saveUrl,
    {
        data: Util.arrayBufferToBlob(fileArrayBuf),
        type: contentType
    },
    function(fileEntry, fileWriter) {

        console.log('file saved!');

        //increment downloaded
        me.completedFiles++;

        //if reached the end, finalize the process
        if (me.completedFiles === me.totalFiles) {

            sendMessage({
                key             : 'download-progresss',
                totalFiles      : me.totalFiles,
                completedFiles  : me.completedFiles
            });

            me.completedFiles = me.totalFiles = me.percentage = me.downloadedFiles = 0;
            delete me.percentages;

            //reload local
            loadLocalFiles(callback);
        }
    },
    function(e) {
        console.log(e);
    }
);

ダウンロード プロセスが完了すると、MediaExplorer がメディア ファイルのリストとメディアを更新します。 プレーヤー ツリー パネルが開きます。