Windows 8桌面的电影应用
在超极本封装的桌面网络应用中观看公共领域和独立电影。将电影应用程序用作本地电影节的离线日程表,以支持独立电影放映。
引言
我正在构建电影应用程序,并使用 encapsulator 2 封装用于超极本,作为桌面应用程序。电影应用程序通过桌面网络应用程序提供超过 150 部公共领域和独立 (Indie) 电影,展示了基于触控的响应能力、HTML5/CSS3 功能以及超极本的视频编码能力。虽然大多数现实世界的活动都只在线上进行,但独立电影的粉丝们仍然喜欢参加真实的电影节来支持本地电影制作人。电影应用程序允许粉丝们在线上与本地电影制作人的作品建立联系,通过参加本地电影节支持电影艺术,并通过使用离线日程表发现下一部优秀的电影。电影制作人可以使用这个强大的视频处理工具更快地编码他们的创意项目。电影应用程序:观看、分享、发布、参加。
下面列出的代码片段展示了触控作为一级公民的地位。滑动手势用于直接操作大型触控目标,将像素投射到屏幕上。精简的代码通过不依赖 wifi,而是使用基于浏览器的本地 sqlite 数据库进行评论和日程的数据存储,从而绕过间歇性、昂贵且耗电的在线查找,从而强调了节电。
拟物化与数字用户界面
90 年代末互联网泡沫破裂后的网页开发吸引了严重依赖拟物化来在线传达其现实世界想法的设计师。拟物化是将熟悉的现实世界对象应用于新环境,以保持一种怀旧舒适的想法。例如,网站用户界面 (UI) 使用皱巴巴的纹理背景,使网站看起来像泛黄的旧纸;深色木纹传达手工制作的粗糙感;拉丝金属营造出高度抛光的感觉。最近的趋势是使用带有柔和纹理的凸版印刷文字在数字用户界面中。这些是假装带来温暖和熟悉感的人工手段,但数字界面不需要旧世界的参考,并且通过去除虚假纹理或其他拟物化可以效率更高。Windows 8 将这一点推向了极致,除非绝对必要,否则会移除所有界面元素。Windows 8 不依赖于按钮反射、木纹或拉丝金属界面,而是要求将重点放在内容上,并创造性地使用具有高度和颜色变化的字体进行设计。
背景
在构建电影应用程序时,我希望以有组织、高效的方式为现代在线观众保存和提供经典公共领域电影。当我观看转码的电影时,我看到了早期电影的伪影和屏幕颗粒。这些不是人工拟物化元素,它们是 1900 年代早期拍摄的真实电影胶片上的实际灰尘和颗粒。
因为拟物化在 2010 年成为界面设计中非常重要的一部分,我将它们融入到我的设计中。我早期为电影应用程序设计时使用了大量的纹理和阴影。

并且大量使用了凸版印刷效果,如下面的徽标所示。凸版印刷存在于传统印刷中。凸版印刷在厚纸上留下非常深的压痕。这传达了对纸张细节的昂贵关注。您可以在我 2011 年 1 月的徽标中看到,我创造了这种在奇特光线纹理上重重印章的印象。“Film”一词和胶片齿轮应该看起来像是被深深蚀刻到这个表面上。尽管背景中有很多东西,但我确实保持了徽标非常简洁。它仍然向我传达了电影的含义。当徽标调整大小以显示为小应用程序图标时,这些细节很多都丢失了。

当我发现设计中不必要的额外元素时,我不得不迭代地从电影应用程序的原始设计中删除。我的原始设计将所有内容都放在您的眼前:标题、导演、日期、时长、描述和海报,在一个长长的垂直滑动中。巨大的页眉和页脚导向其他部分。点击海报将导向一部电影。非常昂贵,因为我提前展示了所有数据,包含了大量的文字,并期望用户一次性吸收所有内容。

我已将其重新设计为一张海报,上面叠加了年份和时长。电影海报,无论何时设计,都包含电影的标题和所有情感理念;每个观众只需一眼就能从我精简的基本元素中看出,他们是否会根据情感反应、时间限制和感兴趣的年份来观看。巨大的页眉和页脚已被徽标取代。超大的文字划分了电影类型。微妙的颜色变化定义了电影类型的类型。这就是触控大放异彩的地方。启动此应用程序后,第一个必需的用户输入是触控手势。向左或向右滑动以查看此电影类型中的其他电影。向上或向下滑动以查看更多电影类型。快速浏览时间日期,滑动以找到感兴趣的条目,点击开始观看。触控,作为一级公民,被赋予了最重要的角色:在第一个屏幕上使用。

感兴趣的用户点击将显示完整的海报、描述、导演以及即时播放的电影。超极本的 HTML5 在这里大放异彩。视频为 h.264 格式,用户可完全控制。多列 CSS3 将大量描述性文本分成几部分,使内容在视觉上与传统报纸一样吸引人。首字下沉,以区分此思想的开始。电影海报内联放置在描述中,这是 CSS3 的另一个功能。正下方是所有相关电影。同样,通过滑动手势调出更多电影海报。因此,我不是依赖下一步按钮,而是使用触控滑动来获取下一组电影选项。不是使用箭头来查找上一部/下一部电影,相关电影再次采用滑动手势。触控成为一级公民。尽管为非触摸屏设备列出了非常小的上一部/下一部文本,但仍鼓励用户尽可能触摸他们的超极本。要移动到下一选项,请使用非常自然的手势滑动大型触摸目标,而不是使用鼠标精度点击微小的按钮或链接目标。滑动手势允许我删除箭头、文本、说明以及其他会分散注意力并影响实际内容的 UI 元素。

使用代码
框架极大地将重复、日常任务的构建从程序员手中解放出来,但代价是文件大小。当前流行的 jQuery 框架有时被用作加速应用程序交付的拐杖,而不是优化代码。jQuery 的未解压、未最小化最新版本重达 260KB。内置函数处理 ajax、多浏览器兼容性、链式调用以及数百种其他框架功能。但在为超极本构建快速触控响应时,我知道封装器包含 Chromium WebKit 浏览器。我不需要链式调用函数或担心跨平台兼容性。我只需要使用 touchmove javascript 函数进行屏幕滑动。Brad Birdsall 漂亮的 swipejs.com 在不进行压缩的情况下只有 8KB。Swipejs 获取多张图像并创建一个基于触摸的滑块,具有 1:1 的移动和弹性边界。通过移除我对 jQuery(260KB)作为综合解决方案的依赖,并使用 swipejs(8KB),我节省了单个库 32 倍的重量,仅仅是为了在基于 Chromium WebKit 的浏览器中使用滑动手势将像素投射到屏幕上。swipe.js 的完整内容如下所示,它控制着屏幕上投射的所有像素。
/* * Swipe 1.0 * * Brad Birdsall, Prime * Copyright 2011, Licensed GPL & MIT * */ window.Swipe = function(element, options) { // return immediately if element doesn't exist if (!element) return null; var _this = this; // retreive options this.options = options || {}; this.index = this.options.startSlide || 0; this.speed = this.options.speed || 300; this.callback = this.options.callback || function() {}; this.delay = this.options.auto || 0; // reference dom elements this.container = element; this.element = this.container.children[0]; // the slide pane // static css this.container.style.overflow = 'hidden'; this.element.style.listStyle = 'none'; this.element.style.margin = 0; // trigger slider initialization this.setup(); // begin auto slideshow this.begin(); // add event listeners if (this.element.addEventListener) { this.element.addEventListener('touchstart', this, false); this.element.addEventListener('touchmove', this, false); this.element.addEventListener('touchend', this, false); this.element.addEventListener('webkitTransitionEnd', this, false); this.element.addEventListener('msTransitionEnd', this, false); this.element.addEventListener('oTransitionEnd', this, false); this.element.addEventListener('transitionend', this, false); window.addEventListener('resize', this, false); } }; Swipe.prototype = { setup: function() { // get and measure amt of slides this.slides = this.element.children; this.length = this.slides.length; // return immediately if their are less than two slides if (this.length < 2) return null; // determine width of each slide this.width = Math.ceil(("getBoundingClientRect" in this.container) ? this.container.getBoundingClientRect().width : this.container.offsetWidth); // return immediately if measurement fails if (!this.width) return null; // hide slider element but keep removeding during setup this.container.style.visibility = 'hidden'; // dynamic css this.element.style.width = Math.ceil(this.slides.length * this.width) + 'px'; var index = this.slides.length; while (index--) { var el = this.slides[index]; el.style.width = this.width + 'px'; el.style.display = 'table-cell'; el.style.verticalAlign = 'top'; } // set start removed and force translate to remove initial flickering this.slide(this.index, 0); // show slider element this.container.style.visibility = 'visible'; }, slide: function(index, duration) { var style = this.element.style; // fallback to default speed if (duration == undefined) { duration = this.speed; } // set duration speed (0 represents 1-to-1 scrolling) style.webkitTransitionDuration = style.MozTransitionDuration = style.msTransitionDuration = style.OTransitionDuration = style.transitionDuration = duration + 'ms'; // translate to given index position style.MozTransform = style.webkitTransform = 'translate3d(' + -(index * this.width) + 'px,0,0)'; style.msTransform = style.OTransform = 'translateX(' + -(index * this.width) + 'px)'; // set new index to allow for expression arguments this.index = index; }, getPos: function() { // return current index position return this.index; }, prev: function(delay) { // cancel next scheduled automatic transition, if any this.delay = delay || 0; clearTimeout(this.interval); // if not at first slide if (this.index) this.slide(this.index-1, this.speed); }, next: function(delay) { // cancel next scheduled automatic transition, if any this.delay = delay || 0; clearTimeout(this.interval); if (this.index < this.length - 1) this.slide(this.index+1, this.speed); // if not last slide else this.slide(0, this.speed); //if last slide return to start }, begin: function() { var _this = this; this.interval = (this.delay) ? setTimeout(function() { _this.next(_this.delay); }, this.delay) : 0; }, stop: function() { this.delay = 0; clearTimeout(this.interval); }, resume: function() { this.delay = this.options.auto || 0; this.begin(); }, handleEvent: function(e) { switch (e.type) { case 'touchstart': this.onTouchStart(e); break; case 'touchmove': this.onTouchMove(e); break; case 'touchend': this.onTouchEnd(e); break; case 'webkitTransitionEnd': case 'msTransitionEnd': case 'oTransitionEnd': case 'transitionend': this.transitionEnd(e); break; case 'resize': this.setup(); break; } }, transitionEnd: function(e) { if (this.delay) this.begin(); this.callback(e, this.index, this.slides[this.index]); }, onTouchStart: function(e) { this.start = { // get touch coordinates for delta calculations in onTouchMove pageX: e.touches[0].pageX, pageY: e.touches[0].pageY, // set initial timestamp of touch sequence time: Number( new Date() ) }; // used for testing first onTouchMove event this.isScrolling = undefined; // reset deltaX this.deltaX = 0; // set transition time to 0 for 1-to-1 touch movement this.element.style.MozTransitionDuration = this.element.style.webkitTransitionDuration = 0; e.stopPropagation(); }, onTouchMove: function(e) { // ensure swiping with one touch and not pinching if(e.touches.length > 1 || e.scale && e.scale !== 1) return; this.deltaX = e.touches[0].pageX - this.start.pageX; // determine if scrolling test has run - one time test if ( typeof this.isScrolling == 'undefined') { this.isScrolling = !!( this.isScrolling || Math.abs(this.deltaX) < Math.abs(e.touches[0].pageY - this.start.pageY) ); } // if user is not trying to scroll vertically if (!this.isScrolling) { // prevent native scrolling e.preventDefault(); // cancel slideshow clearTimeout(this.interval); // increase resistance if first or last slide this.deltaX = this.deltaX / ( (!this.index && this.deltaX > 0 // if first slide and sliding left || this.index == this.length - 1 // or if last slide and sliding right && this.deltaX < 0 // and if sliding at all ) ? ( Math.abs(this.deltaX) / this.width + 1 ) // determine resistance level : 1 ); // no resistance if false // translate immediately 1-to-1 this.element.style.MozTransform = this.element.style.webkitTransform = 'translate3d(' + (this.deltaX - this.index * this.width) + 'px,0,0)'; e.stopPropagation(); } }, onTouchEnd: function(e) { // determine if slide attempt triggers next/prev slide var isValidSlide = Number(new Date()) - this.start.time < 250 // if slide duration is less than 250ms && Math.abs(this.deltaX) > 20 // and if slide amt is greater than 20px || Math.abs(this.deltaX) > this.width/2, // or if slide amt is greater than half the width // determine if slide attempt is past start and end isPastBounds = !this.index && this.deltaX > 0 // if first slide and slide amt is greater than 0 || this.index == this.length - 1 && this.deltaX < 0; // or if last slide and slide amt is less than 0 // if not scrolling vertically if (!this.isScrolling) { // call slide function with slide end value based on isValidSlide and isPastBounds tests this.slide( this.index + ( isValidSlide && !isPastBounds ? (this.deltaX < 0 ? 1 : -1) : 0 ), this.speed ); } e.stopPropagation(); } };
在 HTML 文档中,我只需将滑块列为 ID,并带有 swipe 类。用电影海报图像填充无序列表,以便进行滑动。此时,我已将屏幕上的像素投射从笨重的 250+KB 库简化,并将其瘦身到仅 8KB。英特尔超极本处理器的强大功能应该能够非常流畅地在水平和垂直方向上投射大量这些像素。
<h2>Classics</h2> <div id='slider' class='swipe'> <ul> <li style='display:block'><div><img src="images/classicfilm001.jpg"></div></li> <li style='display:none'><div><img src="images/classicfilm002.jpg"></div></li> <li style='display:none'><div><img src="images/classicfilm003.jpg"></div></li> <li style='display:none'><div><img src="images/classicfilm004.jpg"></div></li> <li style='display:none'><div><img src="images/classicfilm005.jpg"></div></li> </ul> </div> <a href='#' onclick='slider.prev();return false;'>prev</a> <a href='#' onclick='slider.next();return false;'>next</a> <br><br>
在页面最底部,为了保持加载速度,添加对 swipe.js 的引用;在此下方,添加更多 javascript 来指定要操作的 ID,控制每个幻灯片的持续时间(如果用户没有滑动),以及下一张幻灯片的速度。
script src='js/swipe.js' /script // slider var slider = new Swipe(document.getElementById('slider'), { //startSlide: 1, speed: 400, auto: 10000, callback: function(e, pos) { var i = bullets.length; while (i--) { bullets[i].className = ' '; } bullets[pos].className = 'on'; } }), bullets = document.getElementById('position').getElementsByTagName('em'), // tabs tabs = new Swipe(document.getElementById('tabs'), { callback: function(event,index,elem) { setTab(selectors[index]); } }), selectors = document.getElementById('tabSelector').children; for (var i = 0; i < selectors.length; i++) { var elem = selectors[i]; elem.setAttribute('data-tab', i); elem. önclick = function(e) { e.preventDefault(); setTab(this); tabs.slide(parseInt(this.getAttribute('data-tab'),10),300); } } function setTab(elem) { for (var i = 0; i < selectors.length; i++) { selectors[i].className = selectors[i].className.replace('on',' '); } elem.className += ' on'; } // url bar hiding (function() { var win = window, doc = win.document; // If there's a hash, or addEventListener is undefined, stop here if ( !location.hash || !win.addEventListener ) { //scroll to 1 window.scrollTo( 0, 1 ); var scrollTop = 1, //reset to 0 on bodyready, if needed bodycheck = setInterval(function(){ if( doc.body ){ clearInterval( bodycheck ); scrollTop = "scrollTop" in doc.body ? doc.body.scrollTop : 1; win.scrollTo( 0, scrollTop === 1 ? 0 : 1 ); } }, 15 ); if (win.addEventListener) { win.addEventListener("load", function(){ setTimeout(function(){ //reset to hide addr bar at onload win.scrollTo( 0, scrollTop === 1 ? 0 : 1 ); }, 0); }, false ); } } })();
类似的规模和复杂性优化也可以应用于编写 Ajax 代码。当用户对每部电影发表评论时,我不想让电影停止,或者在发布用户一两句评论时重新加载任何其他 UI。我可以使用最基本的 Ajax 功能来处理评论发布,而不是使用检查所有可能用例的 jQuery。2005 年末,在我阅读了 Jesse James Garret 的论文“Ajax:Web 应用程序的新方法”后,我在发布评论表单中的信息时使用了以下代码。
var http_request = false; function makePOSTRequest(url, parameters) { http_request = false; if (window.XMLHttpRequest) { // Mozilla, Safari,...</font> http_request = new XMLHttpRequest(); if (http_request.overrideMimeType) { // set type accordingly to anticipated content type //http_request.overrideMimeType('text/xml'); http_request.overrideMimeType('text/html'); } } else if (window.ActiveXObject) { // IE try { http_request = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { http_request = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) {} } } if (!http_request) { alert('Cannot create XMLHTTP instance'); return false; } http_request.onreadystatechange = alertContents; http_request.open('POST', url, true); http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); http_request.setRequestHeader("Content-length", parameters.length); http_request.setRequestHeader("Connection", "close"); http_request.send(parameters); }
我非常尊敬微软在 1999 年创建 XMLHTTP ActiveX 控件的超前思维,并且他们在 2000 年悄悄将其用于 Outlook Web Access,这比它被更广泛的世界所认可早了整整 5 年,这仍然令我感到惊讶。但今天我不需要在代码中检查旧的 IE 实现,也不需要在每次 Ajax 请求时检查 XMLHttpRequest 的存在。通过移除对 IE 所需的两个版本 ActiveX 对象的检查,我可以进一步优化我的 Ajax 代码,因为我再次知道,在这个封装的 Web App 中,只需要考虑 Chromium,并且 XMLHttpRequest 在这个浏览器中确实存在。上面的代码是我们自 2005 年末以来一直使用的标准 Ajax。首先创建一个变量,然后检查不同的浏览器,设置为 HTML 而不是 XML,然后检查多个版本的 IE ActiveXObjects,如果 XMLHTTP 不存在则不继续。所有检查 IE 浏览器和 XMLHttpRequest 是否存在的代码都可以删除。因此,用于获取和返回我的评论帖子的简单 Ajax 函数就变成了几行非常简洁的代码,如下所示。它假设您在现代超极本封装的应用程序中拥有所有 Chromium WebKit 工具,不要浪费时间检查您已知存在的东西。要发出 POST 请求,请创建新的 XMLHttpRequest。然后将 override mime type 设置为接受返回的 HTML 而不是 XML。完成。onreadystatechange、open、setRequestHeader 和 send 行不会改变。
function makePOSTRequest(url, parameters) { http_request = new XMLHttpRequest(); if (http_request.overrideMimeType) { http_request.overrideMimeType('text/html'); } }
在修剪我的 UI 时,我开始思考这些优化。我识别并移除了设计中的许多拟物化元素,并精简了巨大而不必要的 UI 元素。追求效率也导致了许多代码行的削减。摆脱了所有这些非必要的检查和所有可能的异常情况,编写代码实际上很有趣。并且拥有小巧的代码库也很有帮助,因为 UI 响应非常迅速。
超极本的 Web App Chromium 浏览器内置的 CSS3 允许我设计出非常美观的布局。首字下沉应用于段落中第一个子元素的第一个字母,向左浮动,着色,调整大小,并在字符周围留有一些空间。代码如下所示。如果您查看代码下方的海报图像,您会看到第一个字符巨大,区分了每部电影的新思想。
p:first-child:first-letter { float: left; color: #5499c5; font-size: 5em; line-height:0.5em; display: inline; padding: 0.2em 0.1em 0 0.2em; text-transform: capitalize; }

#multicolumnElement { width:960px; -webkit-column-width:300px; -webkit-column-gap:30px; margin-left:auto; margin-right:auto; height: 600px; }
另一个类似的 CSS3 漂亮新增功能:多列布局。如上所示,CSS3 代码为我提供了宽度为 300 像素、间距为 30 像素的列。无论您的设备宽度如何,页面居中,列高应为 600 像素,并且相当均衡。在 HTML 和 CSS 的前几代中,要实现这样的布局需要太多的变通方法。
Intel App Up 封装器集成了 Chromium WebKit 浏览器,这允许大量的 CSS3 更新。我使用多列布局在横向模式下优美地分隔文本。我将不得不找出如何使用 CSS3 Media Queries 来检查不同数量的列,在设备处于纵向模式时具有更高的列高。
关注点
在构建电影应用程序时,我的第一个想法是简单地收集公共领域的电影并在移动应用程序商店中付费分发。所以今年早些时候,我准备好我的设备,在西南偏南(SXSW)上租了一个展位,并在德克萨斯州奥斯汀待了 10 天。我的供应商证件展示在装有 SXSW 2012 音乐、电影和科技信息的袋子上

西南偏南是一个科技、电影和音乐交汇的创意环境。身处其中所产生的关系绝对令人惊叹。我设计了横幅、桌布和明信片。

以及精美的凸版印刷名片。在这种情况下,我实际上查看了所有我一年前的拟物化标志设计,并创建了一个模板,展示给芝加哥的 Rohner Letterpress。他们根据我的插图,制作了一些模具,并将这些设计深深地压印在非常厚的纸上。凸版印刷在现实生活中看起来非常棒。当你用拇指划过卡片时,你真的可以感受到压痕。它能立即吸引注意力,每个人都会瞥一眼标志。每个人。我现在可以看到真实的凸版印刷设计(字母实际压印在厚纸上)和我平面数字屏幕上那些拟物化的、大部分看起来很假的 3D 效果之间的区别。Windows 8 的设计标准要求摒弃纹理、阴影、反射和渐变效果的伪装,转而优化现代数字屏幕。



我希望能三月份在奥斯汀展出,并为电影应用吸引粉丝。它奏效了;我做得很好。一些博主喜欢它,下载了它,后来过来表示他们喜欢电影应用的简洁性。
我为此做好了准备;准备好派发名片,让人们欣赏我的应用程序。我没有为电影制作人和电影节导演的建议做好准备。所以,当我花前两个小时演示并非常愉快地展示我的公共领域电影收藏时,我没有意识到几乎所有奥斯汀的参会者都是创作者,而不仅仅是消费者。我不小心偶然发现了一组感兴趣的新市场。我不仅找到了想观看电影的消费者,还遇到了独立电影的创作者,以及展示这些独立电影的电影节组织者。他们给了我大量建议,告诉我如何将他们的作品包含在我的应用程序中。在许多类似的情况下,我以前只是记下一些信息,然后说几周后会提供一些更新。但是有一位特别感兴趣、执着的电影制作人,他真的希望他的电影能以数字形式被看到。他递给我一张他 25 分钟电影的 DVD,并问我会在 SXSW 待多久。他还想知道我是否可以将他的电影转码并展示在我的电影应用程序中。他正和一位朋友站在一起,这位朋友经营着一个本地电影节,这位电影节导演想知道我是否可以展示一个带有电影制作人简介的本地化电影日程表,这样她就可以在她的电影节上将电影应用程序分发给她的精通技术的与会者。
大多数应用开发者没有机会实时识别一个新兴市场,眼看着它在眼前形成。而在 SXSW,开幕后的头两个小时内,我看到了并开始解决一个更大的问题。
前往展厅的人流出现了一段间歇。一位同行的摊位卖家告诉我,Vimeo 赞助的影院开始认真地连续放映电影。技术人员正忙于会议。卖家说,行动会有一段停顿;人流会像这样,大约两个小时内几乎没有参观者。于是我拿出笔记本电脑,即时做出了简单的决定,决定在一个小时内合理地构建什么,以便向接下来停下来的电影制作人和电影节导演展示。
首先是新屏幕,标准 ajax 轮询以获取实时日程信息,并通过 wifi 为每个电影节下载。下次她再来时向电影节导演展示这个——简单的日程表,通过 wifi 进行 ajax 更新,以防电影放映时间在最后一刻临时更改。
然后打开 HandBrake,将这张 DVD 抓取成 h.264 格式,这样我就可以展示给电影制作人。这部 25 分钟的影片将需要几分钟从 DVD 复制到我的硬盘,然后大约 25 分钟进行转换,这样我就可以在他下次停下来时将他的影片包含在我的演示中。

所以在我的笔记本电脑上进行编码的同时,我快速绕着这个巨大的 500 个摊位区域走了一圈,了解一下这里还有什么。很多位置感知应用程序。很多社交网络。几乎每个人都遇到了问题,因为在这个技术精通的观众中,WiFi 已达到最大容量。社交应用程序无法连接,我看到的少数演示都被敷衍了事,因为在这个每个人都在不断使用设备上网的走廊里,很少有网络连接。这些社交应用程序的最佳部分无法演示,因为没有连接它们就死了。位置感知应用程序正在耗尽设备电池,这些供应商正在寻找插座和时间来为他们带来展示作品的少量设备充电。因为在这个时尚技术人群中对 WiFi 访问的需求过大,通过 WiFi 进行位置感知轮询正在耗尽电池。用户下载这些应用程序后,在参观摊位时就删除了它们,尽管它们背后的概念是非常有思想的应用程序。
立即做出决定。当我走回我的展位时,距离我这边的展区人流高峰还有大约一个小时,我必须减少电池浪费,并允许访问关键数据,而无需完全依赖 WiFi。放弃我的 Ajax 轮询调度程序,Chromium 浏览器有一个捆绑的 SQLite 数据库,将数据本地存储在设备上。放弃重复轮询并本地存储数据,以节省电池。如果电影院像 SXSW 展厅一样,使用电影应用程序的观众可能会遇到同样的 WiFi 缺乏和电池耗尽问题,所以重新思考并优化电影应用程序以防止电池耗尽。
我没有为每部电影从云数据库中轮询评论,而是在远程服务器上设置了一个 PHP 脚本,将该电影的所有评论导出到一个文本文件中。这个文本文件被导入到应用程序评论部分的一个 div 中。用户发布的评论直接存储到本地 SQLite 存储中,然后显示在下载的倒序评论正上方的 div 中。检查 WiFi 连接。如果存在,将此评论发布到云数据库。
如果上一行没有 wifi,则在本地 sqllite 存储中设置一个标志,表示评论未上传到云端。她点击的下一部电影,她的 sqllite db 评论将被提取,然后在后台发布到云端。
这改变了我的应用程序,从轮询以获取实时评论信息和消耗电池,变为仅在点击电影时检索,仅本地发布到 sqllite 数据库,然后在后台将用户评论发布到云端。
另一个优化:移除重复的 Ajax 轮询以获取日程信息。
采用相同的优化方法。检查所有云端存储的电影日期、放映地点,然后导出到文本文件。当用户打开应用程序时,检查是否有 Wi-Fi。导入最新的日程文本文件。不再进行轮询。
此时文件已经编码完成,我在演示中编写了非常简单的插入、更新代码,将编码后的电影打包到应用程序中,然后部署到平板电脑和手机。
几分钟后,电影制作人和电影节导演就赶过来,在去吃排骨的路上又看了一眼 App。我向他们展示了他在 App 中的电影,然后运行了本地化日程表。整个过程都没有 Wi-Fi。没关系,这个演示所需的一切都打包在 App 中。我问了关于日程安排的问题,以及他们多久会担心临时更改。电影节导演说总是会有临时更改,但绝大多数独立电影节都是提前几个月计划好的。小型电影节最大的担忧是参会人数。但为了有一个出色的节目,日程表会提前几个月确定。我问了电池续航与实时日程表的问题,他们都向我保证,在 App 中获得 95% 正确的数据,并在活动现场告知参会者重大更改要容易得多。我对轮询最新数据却无法实时更新表示担忧。答复是,小型人手不足的电影节现场压力很大,无法安排专职人员在线发布更改。所有紧急更改都会通过非常响亮的麦克风一遍又一遍地当面重复,问我是否可以尽快将这个打包,以便他们能在几周内用于他们的电影节?电影 App 的销售开始正式启动。我最初保存公共领域电影的想法是一个不错的销售策略。但提供当前的独立电影和列出离线日程对这批电影制作人和电影节导演来说更具吸引力。
不幸的是,我需要管理的设备太多,无法在 SXSW 期间编码足够的电影制作人作品。七个月后,我仍然在与我在 SXSW 2012 遇到的电影制作人交换电子邮件和转码电影,我们都有时间互相跟进。
我应该提到,作为一名独立应用开发者,最大的缺点就是无法无处不在。英特尔实际上在 SXSW 期间演示了超极本。在我短暂参观其他展位的时间里,我没能看到他们的作品。作为一名开发者,我正在为我的笔记本电脑、设备、平板电脑和手机而挣扎。我的电影应用有任何改动,我都必须去找电源,启动我的笔记本电脑,编译,部署到多个设备,然后保护我的笔记本电脑。当我在一个遥远的城市拥挤的展位上推广我的电影应用时,这并不是最佳选择。
拥有一台支持触控的超极本将使我无需携带所有这些其他演示设备。超极本的轻薄特性将使我能够在其触控界面上进行演示。大多数展会要求我作为供应商连续工作 8 小时。虽然提供了电源,但无需担心电量耗尽是奢侈的。超极本的电池续航能力应该能为我在电影应用中演示视频、使用滑动手势在屏幕上投射像素,并全天运行提供足够的电量。
英特尔视频编码优化的强大功能将帮助我吸引更多电影制作人。Windows 团队在优化 Windows 8 编码视频的方式上花费了大量时间,与 Windows 7 相比。请查看下方“来自构建 Windows 8 博客”的链接,Windows 8 团队在其中列出了他们内置于 Windows 8 的广泛媒体功能。特别查看底部他们提供的 8 分钟视频。他们谈论了在 6:30 - 6:50 编码视频文件,仅仅 20 秒就展示了如此惊人的性能改进。
在我进行的许多测试中,在双启动 Windows 8 / Windows 7 的机器上,相同文件的编码速度几乎提高了 25%。超极本处理器可能会帮助我进一步缩短视频编码处理时间。需要澄清的是,我正在编码 150 个文件,平均每个近 2 小时。节省 30 分钟,150 次意味着节省了近 75 小时。这在所有 Windows 8 文档中都如此轻描淡写。电影制作人一生都在等待编码,现在可以多出 25% 的时间。而超极本的优化应该会进一步减少编码时间。

如果明年我作为参展商参加 SXSW,我将带几台超极本来演示电影编码的速度。对于电影制作人来说,节省一两分钟意味着可以更专注于获得完美的音效、灯光,或者从他们的演员那里获得更好的表演。平均而言,在 Windows 8 中编码一部一小时的电影,比在桌面电脑上使用 Windows 7 编码相同的电影节省了我 15 分钟。这就是真正的力量:在相同的时间内完成更多工作。
最初,我希望构建电影应用程序来保存公共领域电影。我在 2012 年 3 月的 SXSW 上进行了演示,发现出现了两个全新的机会:希望在电影应用程序中展示其作品的电影制作人,以及希望提供最少离线日程的小型本地电影节导演。

这个超极本 Windows 8 桌面版电影应用程序利用了强大的 HTML5 和 CSS3 功能。我可以优化电池续航,提供离线数据,最重要的是,展示如何通过使用现代英特尔超极本仪器的优化,将视频文件编码速度提高 25%。在我的最佳视频比特率下,每小时编码电影大约产生 500MB。我目前正准备在明年的 2013 年西南偏南电影节上演示 150 部电影。
这一次,我可以鼓励电影制作人编码并上传他们的作品,这样我们就可以在电影节期间在电影应用程序中展示他们的作品,而不是等待几周或几个月之后。
电影应用程序:观看、分享、发布、参加
参考文献
- Brad Birdsall 的 8KB Swipe JS 库
- Jesse James Garret 的论文:“Ajax:Web 应用程序的新方法”
- 罗纳凸版印刷
- 微软构建丰富可扩展的媒体平台
历史
芝加哥时间晚上 10:47:修改了标题部分,在末尾添加了参考部分,对文本进行了大量修订以加强 CSS3 代码,删除了重复的“播放”文本,缩短了句子长度,并重新排序以提高可读性。
芝加哥时间凌晨 2:00:多次修改段落,以非常清晰地指出英特尔 App Up 封装器集成了 Chromium WebKit 浏览器,以支持 HTML5 和 CSS3 功能。
芝加哥时间晚上 9:30:添加了一张显示本地电影制作人功能和离线日程表的图片,将复数词“exists”改为“exist”以确保正确性,将一句句子澄清为两句,以加强其含义。
芝加哥时间下午 7:15:添加了来自网站的图片引用,展示了 CSS3 首字下沉和多列如何在横向模式下使用。切换了代码片段并添加了一张图片,指出 CSS3 首字下沉/列代码的使用方式和位置。修正了一个粗心大意的地方,即用户性别在同一句话中发生了变化。在第二段中添加了一个句号。添加了更多 JavaScript 以说明滑动手势的使用方式。移除了 PRE 标签中的红色格式。
芝加哥时间下午 3 点:增加了 3 个来自网站的图片引用,并添加了一些 CSS3 代码列表。
芝加哥时间上午 10 点:增加了 9 个来自网站的引用,因为压缩的图片上传在文章中不可见。