Sencha Ext JS로 앱 빌드

이 문서의 목표는 Sencha Ext JS를 사용하여 Chrome 앱 빌드를 시작하는 것입니다. 프레임워크입니다 이 목표를 달성하기 위해 Sencha에서 빌드한 미디어 플레이어 앱을 자세히 살펴보겠습니다. 소스 코드API 문서는 GitHub에서 확인할 수 있습니다.

이 앱은 PC에 연결된 미디어 기기를 포함하여 사용자의 사용 가능한 미디어 서버를 검색하고 네트워크를 통해 미디어를 관리하는 소프트웨어입니다. 사용자는 미디어를 둘러보거나 네트워크를 통해 재생하거나 저장할 수 있습니다. 있습니다.

다음은 Sencha Ext JS를 사용하여 미디어 플레이어 앱을 빌드하려면 반드시 해야 하는 주요 작업입니다.

  • 매니페스트, manifest.json를 만듭니다.
  • 이벤트 페이지, background.js 만들기
  • Sandbox 앱의 로직입니다.
  • Chrome 앱과 샌드박스 파일 간에 통신합니다.
  • 미디어 서버 탐색
  • 미디어를 탐색하고 재생합니다.
  • 미디어를 오프라인으로 저장합니다.

매니페스트 만들기

모든 Chrome 앱에는 Chrome에서 실행해야 하는 정보가 포함된 매니페스트 파일이 필요합니다. 있습니다. 매니페스트에 표시된 대로 미디어 플레이어 앱이 'offline_enabled' 상태입니다. 미디어 애셋은 로컬에 저장되고 연결 여부와 관계없이 액세스 및 재생됩니다.

"샌드박스" 필드는 앱의 기본 로직을 고유한 출처에 샌드박스 처리하는 데 사용됩니다. 모두 샌드박스 처리됨 Chrome 앱 콘텐츠 보안 정책에서 제외되지만 Chrome 앱 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 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 앱 소켓 API를 사용하여 미디어 플레이어 앱을 검색하고 미디어 서버에서 미디어 데이터를 수신합니다. Upnp.js이(가) 사용하는 또 다른 방법 soapclient.js를 사용하여 미디어 서버 데이터를 파싱합니다. 이 섹션의 나머지 부분에서는 더 자세히 살펴보겠습니다

메시지 게시

사용자가 미디어 플레이어 앱 중앙의 미디어 서버 버튼을 클릭하면 MediaServers.js discoverServers()를 호출합니다. 이 함수는 먼저 처리되지 않은 검색 요청이 있는지 확인하고, true인 경우 새 요청을 시작할 수 있도록 이를 취소합니다. 다음으로 컨트롤러는 키 upnp-디스커버리 및 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는 새 미디어 서버를 발견하면 기기에 관한 설명을 검색하여 미디어 서버 데이터를 탐색하고 파싱하는 Soaprequest soapclient.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에 메시지를 게시합니다. 키:

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 소개에서 자세히 알아보세요.

사용자가 파일을 하나 이상 선택하고 '오프라인으로 설정'을 시작하면 프로세스가 시작됩니다. 있습니다. 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에서 미디어 파일 목록과 미디어를 업데이트합니다. 플레이어 트리 패널