Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(239)

Unified Diff: chrome/common/extensions/docs/templates/articles/sencha_framework.html

Issue 10985050: New getting started tutorial for sencha media player app. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | chrome/common/extensions/docs/templates/private/apps_sidenav.html » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/common/extensions/docs/templates/articles/sencha_framework.html
diff --git a/chrome/common/extensions/docs/templates/articles/sencha_framework.html b/chrome/common/extensions/docs/templates/articles/sencha_framework.html
new file mode 100644
index 0000000000000000000000000000000000000000..a56f24dbc2b3cdfd4c35a2e12f60671e4ee29ace
--- /dev/null
+++ b/chrome/common/extensions/docs/templates/articles/sencha_framework.html
@@ -0,0 +1,578 @@
+<meta name="doc-family" content="apps">
+<h1>Build Apps with Sencha Ext JS</h1>
+
+<p>
+The goal of this doc is to get you started
+on building packaged apps with the
+<a href="http://www.sencha.com/products/extjs">Sencha Ext JS</a> framework.
+To achieve this goal,
+we will dive into a media player app built by Sencha.
+The <a href="https://github.com/GoogleChrome/sencha-video-player-app">source code</a>
+and <a href="http://senchaprosvcs.github.com/GooglePlayer/docs/output/#!/api">API Documentation</a> are available on GitHub.
+</p>
+
+<p>
+This app discovers a user's available media servers,
+including media devices connected to the pc and
+software that manages media over the network.
+Users can browse media, play over the network,
+or save offline.
+</p>
+
+<p>Here are the key things you must do
+to build a media player app using Sencha Ext JS:
+</p>
+
+<ul>
+ <li>Create manifest, <code>manifest.json</code>.</li>
+ <li>Create <a href="app_lifecycle.html#eventpage">event page</a>,
+ <code>background.js</code>.</li>
+ <li><a href="app_external.html#sandboxing">Sandbox</a> app's logic.</li>
+ <li>Communicate between packaged app and sandboxed files.</li>
+ <li>Discover media servers.</li>
+ <li>Explore and play media.</li>
+ <li>Save media offline.</li>
+</ul>
+
+<h2 id="first">Create manifest</h2>
+
+<p>
+All packaged apps require a
+<a href="manifest.html">manifest file</a>
+which contains the information Chrome needs to launch apps.
+As indicated in the manifest,
+the media player app is "offline_enabled";
+media assets can be saved locally,
+accessed and played regardless of connectivity.
+</p>
+
+<p>
+The "sandbox" field is used
+to sandbox the app's main logic in a unique origin.
+All sandboxed content is exempt from the packaged app
+<a href="app_csp.html">Content Security Policy</a>,
+but cannot directly access the packaged app APIs.
+The manifest also includes the "socket" permission;
+the media player app uses the <a href="socket.html">socket API</a>
+to connect to a media server over the network.
+</p>
+
+<pre>
+{
+ "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"
+ ]
+ }
+ ]
+}
+</pre>
+
+<h2 id="second">Create event page</h2>
+
+<p>
+All packaged apps require <code>background.js</code>
+to launch the application.
+The media player's main page, <code>index.html</code>,
+opens in a window with the specified dimensions:
+</p>
+
+<pre>
+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;
+ });
+
+});
+</pre>
+
+<h2 id="three">Sandbox app's logic</h2>
+
+<p>Packaged apps run in a controlled environment
+that enforces a strict <a href="app_csp.html">Content Security Policy (CSP)</a>.
+The media player app needs some higher privileges to render the Ext JS components.
+To comply with CSP and execute the app logic,
+the app's main page, <code>index.html</code>, creates an iframe
+that acts as a sandbox environment:
+
+<pre>
+&lt;iframe id="sandbox-frame" class="sandboxed" sandbox="allow-scripts" src="sandbox.html">&lt;/iframe>
+</pre>
+
+<p>The iframe points to <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/sandbox.html">sandbox.html</a> which includes the files required for the Ext JS application:
+</p>
+
+<pre>
+&lt;html>
+&lt;head>
+ &lt;link rel="stylesheet" type="text/css" href="resources/css/app.css" />'
+ &lt;script src="sdk/ext-all-dev.js">&lt;/script>'
+ &lt;script src="lib/ext/data/PostMessage.js">&lt;/script>'
+ &lt;script src="lib/ChromeProxy.js">&lt;/script>'
+ <script src="app.js"></script>
+&lt;/head>
+&lt;body></body>
+&lt;/html>
+</pre>
+
+<p>
+The <a href="http://senchaprosvcs.github.com/GooglePlayer/docs/output/source/app.html#VP-Application">app.js</a> script executes all the Ext JS code and renders the media player views.
+Since this script is sandboxed, it cannot directly access the packaged app APIs.
+Communication between <code>app.js</code> and non-sandboxed files is done using the
+<a href="https://developer.mozilla.org/en-US/docs/DOM/window.postMessage">HTML5 Post Message API</a>.
+</p>
+
+<h2 id="four">Communicate between files</h2>
+
+<p>
+In order for the media player app to access packaged app APIs,
+like query the network for media servers, <code>app.js</code> posts messages
+to <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/index.js">index.js</a>.
+Unlike the sandboxed <code>app.js</code>,
+<code>index.js</code> can directly access the packaged app APIs.
+</p>
+
+<p>
+<code>index.js</code> creates the iframe:
+</p>
+
+<pre>
+var iframe = document.getElementById('sandbox-frame');
+
+iframeWindow = iframe.contentWindow;
+</pre>
+
+<p>
+And listens for messages from the sandboxed files:
+</p>
+
+<pre>
+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);
+</pre>
+
+<p>
+In the following example,
+<code>app.js</code> sends a message to <code>index.js</code>
+requesting the key 'extension-baseurl':
+</p>
+
+<pre>
+Ext.data.PostMessage.request({
+ key: 'extension-baseurl',
+ success: function(data) {
+ //...
+ }
+});
+</pre>
+
+<p>
+<code>index.js</code> receives the request, assigns the result,
+and replies by sending the Base URL back:
+</p>
+
+<pre>
+function extensionBaseUrl(data) {
+ data.result = chrome.extension.getURL('/');
+ iframeWindow.postMessage(data, '*');
+}
+</pre>
+
+<h2 id="five">Discover media servers</h2>
+
+<p>
+There's a lot that goes into discovering media servers.
+At a high level, the discovery workflow is initiated
+by a user action to search for available media servers.
+The <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/app/controller/MediaServers.js">MediaServer controller</a>
+posts a message to <code>index.js</code>;
+<code>index.js</code> listens for this message and when received,
+calls <a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/lib/Upnp.js">Upnp.js</a>.
+</p>
+
+<p>
+The <code>Upnp library</code> uses the packaged app
+<a href="app_network.html">socket API</a>
+to connect the media player app with any discovered media servers
+and receive media data from the media server.
+<code>Upnp.js</code> also uses
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/lib/soapclient.js">soapclient.js</a>
+to parse the media server data.
+The remainder of this section describes this workflow in more detail.
+</p>
+
+<h3 id="post">Post message</h3>
+
+<p>
+When a user clicks the Media Servers button in the center of the media player app,
+<code>MediaServers</code> calls <code>discoverServers()</code>.
+This function first checks for any outstanding discovery requests,
+and if true, aborts them so the new request can be initiated.
+Next, the controller posts a message to <code>index.js</code>
+with a key upnp-discovery, and two callback listeners:
+</p>
+
+<pre>
+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');
+ }
+});
+</pre>
+
+<h3 id="call">Call upnpDiscover()</h3>
+
+<p>
+<code>index.js</code> listens
+for the 'upnp-discover' message from <code>app.js</code>
+and responds by calling <code>upnpDiscover()</code>.
+When a media server is discovered,
+<code>index.js</code> extracts the media server domain from the parameters,
+saves the server locally, formats the media server data,
+and pushes the data to the <code>MediaServer</code> controller.
+</p>
+
+<h3 id="parse">Parse media server data</h3>
+
+<p>
+When <code>Upnp.js</code> discovers a new media server,
+it then retrieves a description of the device
+and sends a Soaprequest to browse and parse the media server data;
+<code>soapclient.js</code> parses the media elements by tag name
+into a document.
+</p>
+
+<h3 id="connect">Connect to media server</h3>
+
+<p>
+<code>Upnp.js</code> connects to discovered media servers
+and receives media data using the packaged app socket API:
+</p>
+
+<pre>
+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);
+ });
+ });
+});
+</pre>
+
+
+<h2 id="six">Explore and play media</h2>
+
+<p>
+The
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/app/controller/MediaExplorer.js">MediaExplorer controller</a>
+lists all the media files inside a media server folder
+and is responsible for updating the breadcrumb navigation
+in the media player app window.
+When a user selects a media file,
+the controller posts a message to <code>index.js</code>
+with the 'play-media' key:
+</p>
+
+<pre>
+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
+ }
+ });
+ }
+},
+</pre>
+
+<p>
+<code>index.js</code> listens for this post message and
+responds by calling <code>playMedia()</code>:
+</p>
+
+<pre>
+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( 'webkitTransitionEnd', animEnd, false );
+ animEnd = null;
+ };
+
+ //load media
+ mediaEl.src = url;
+ mediaEl.load();
+
+ //animate in player
+ playerCt.addEventListener( 'webkitTransitionEnd', animEnd, false );
+ playerCt.style.webkitTransform = "translateY(0)";
+
+ //reply postmessage
+ data.result = true;
+ sendMessage(data);
+}
+</pre>
+
+<h2 id="seven">Save media offline</h2>
+
+<p>
+Most of the hard work to save media offline is done by the
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/lib/filer.js">filer.js library</a>.
+You can read more this library in
+<a href="http://ericbidelman.tumblr.com/post/14866798359/introducing-filer-js">Introducing filer.js</a>.
+</p>
+
+<p>
+The process kicks off when a user selects one or more files
+and initiates the 'Take offline' action.
+The
+<a href="https://github.com/GoogleChrome/sencha-video-player-app/blob/master/app/controller/MediaExplorer.js">MediaExplorer controller</a> posts a message to <code>index.js</code>
+with a key 'download-media'; <code>index.js</code> listens for this message
+and calls the <code>downloadMedia()</code> function
+to initiate the download process:
+</p>
+
+<pre>
+function downloadMedia(data) {
+ DownloadProcess.run(data.params.files, function() {
+ data.result = true;
+ sendMessage(data);
+ });
+ }
+</pre>
+
+<p>
+The <code>DownloadProcess</code> utility method creates an xhr request
+to get data from the media server and waits for completion status.
+This initiates the onload callback which checks the received content
+and saves the data locally using the <code>filer.js</code> function:
+</p>
+
+<pre>
+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);
+ }
+);
+</pre>
+
+<p>
+When the download process is finished,
+<code>MediaExplorer</code> updates the media file list and the media player tree panel.
+</p>
+
+<p class="backtotop"><a href="#top">Back to top</a></p>
« no previous file with comments | « no previous file | chrome/common/extensions/docs/templates/private/apps_sidenav.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698