用TVML开发tvOS应用教程

作者:Kelvin Lau 原文地址:Beginning tvOS Development with TVML Tutorial

在2015年9月9日的产品发布会中,Apple宣布了新一代的Apple TV以及tvOS,并且在tvOS中集成了App Store。这使得我们多年以来想在Apple TV上开发专属应用的梦想成真了。

我和其他教程团队的成员已经开始深入研究tvOS SDK,并且正在努力为你们准备一些有价值的tvOS教程。在你开这篇文章之前,Chris Wagner已经写了一篇关于tvOS初步印象的文章,我也基于这篇文章,设计了第一个tvOS的教程。

译者注:可参阅Chris Wagner文章的中译版一个iOS开发者对tvOS SDK的初探

在这篇教程中,你将会使用TVML开发你的第一款tvOS应用。信不信由你,你将会使用JavaScript管理你应用的逻辑以及创建TVML模板来展现你应用的UI。

当教程结束后,你应该可以基本理解如果通过TVML和TVJS管理、控制tvOS应用。现在就我们开始吧。

注意:该教程需要Xcode7.1或更高的版本,你们可以在这里下载。虽然你们可以跟着该教程一步一步的进行操作,但我还是建议你们能储备一些基本的JavaScript知识。

选择开发方式

Apple为开发tvOS应用提供了两种方式:

  1. TVML Apps:这类应用是使用完整的新开发技术开发的,比如TVML、TVJS、TVMLKit。在稍后我会解释这些简称的含义以及如何使用它们。
  2. Custom Apps:这类应用是使用我们已经比较熟悉的开发技术进行开发的,比如大家熟知的一些iOS框架和特性,像Storyboard、UIKit、Auto Layout等。

这两种方式没有孰优孰劣之分,都是Apple推荐的方法,只是按需所取,以及你更想尝试哪种方式。

在这篇教程中,你们的目标是开发以个能播放RWDevCon讨论视频的tvOS应用:

tvOS-1

虽然用上述两种方式都可以开发这个应用,但是使用TVML会更加容易一些。所以这就是你在这篇教程中要学和要做的东西。

什么是TVML?

正如我刚才提到的,第一种开发tvOS应用的方式是通过TVML、TVJS和TVMLKit这些新的技术实现的。如果你们对这些简称比较陌生,不要惊慌,因为他们本就是新鲜玩意。这里我简单解释一下:

  • TVML是一种XML格式,基于“Television Markup Language”。
  • TVJS是基于JavaScript APIs的脚本语言,它可以根据TVML中定义的内容展示应用。
  • TVMLKit是连接TVML、TVJS和原生tvOS应用的工具包。

如果你是一名经常使用原生API开发原生iOS应用的开发者,那么看到这些类似前端的技术可能会有点退缩。但希望你能保持一颗学习的心,学习了解这些新的强大的特性。

我在这列举一个非常典型的Apple TV的应用场景。大家可以想象一下:你们想把一些内容或信息展现给用户,这些内容和信息储存在你们的服务器上,并且这些内容的格式、查询方式都服务于iPhone或iPad中的应用,那么你肯定会希望你的tvOS中的应用也能方便的使用这些内容,并做到与iOS中应用有相似的展现、查询、导航方式。

tvOS-2

上述的这个场景恰恰是我们这片教程中的场景。我们已经有RWDevCon网站,上面有许多技术讨论视频,所以运用TVML模板应该很容易实现。并且我们也没有很严格的用户界面的需求,所以我们可以简单方便的使用Apple提供的UI模板

tvOS-3

简而言之:

  • 开发TVML App:如果你主要是通过tvOS应用展现一些内容,不论是音频、视频、文本、图片,并且你已经有服务器存储这些资源。那么使用TVML开发是不错的选择。
  • 开发Custom App:如果你希望用户不只是被动的通过你的tvOS应用观看或收听内容,而是希望用户与应用有更多的交互,给用户高质量的用户体验。那么你应该选择使用iOS的相关技术开发自定义的应用。

现在你们已经大概了解了TVML是如何工作的,以及我们为什么要在这篇教程中使用TVML开发tvOS应用。想要更深入的了解,最好的办法就是由你们在实践中去学习、理解了。让我们开始动手吧!

准备工作

首先你们要确保已经下载并安装了Xcode7.1或更高版本。

然后通过 File\New\Project 创建新工程,在侧边栏选择 tvOS\Application\Single View Application 模板,然后点击 Next

tvOS-4

项目名称输入 RWDevCon ,语言选择 Swift ,确保下面的两个复选框为未选中状态,也就是不使用Core Data和单元测试,然后点击 Next

tvOS-5

选择一个目录,点击 Save 保存你的项目。Xcode会为你创建一个带有Storyboard的空工程(如果你开发自定义UI的tvOS应用,那么你需要使用Storyboard)。

然而在该教程中你不需要使用Storybard,因为我们会使用TVML来展示应用的UI,而不是用Storybard去设计UI。所以将 Main.storyboardViewController.swift 删去,在提示框中选择 Move To Trash 彻底删除。

接着打开 Info.plist 文件,删掉Main storybaord file base name属性。最后添加新的属性App Transport Security Settings(区分大小写),以及它的子属性Allow Arbitrary Loads,并将其值设为YES

tvOS-6

注意:在iOS9中,Apple不允许应用链接非HHTPS协议的服务,所以刚才的操作是很有必要的,因为在该教程中,你们将会以HTTP协议访问本地的服务器,所以你需要在Info.plist中添加上述属性以便允许应用通过HTTP协议访问服务器。

加载你的TVML

tvOS应用的生命周期开始于AppDelegate。在这里,你将创建TVApplicationController以及应用上下文,并将它们传给主要的JavaScript文件。

打开AppDelegate.swift并做下面这些事:

  • 删除所有的方法。
  • 导入TVMLKit
  • 使AppDelegate遵循TVApplicationControllerDelegate协议。

当完成这些事后,你的AppDelegate.swift看起来应该像这样:


import UIKit
import TVMLKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, TVApplicationControllerDelegate {

var window: UIWindow?

}

接着添加下面这些属性:


var appController: TVApplicationController?
static let TVBaseURL = "http://localhost:9001/"
static let TVBootURL = "\(AppDelegate.TVBaseURL)js/application.js"

TVApplicationControllerTVMLKit中的一个类,它负责与你的服务器的交互。TVBaseURLTVBootURL包含了你的服务器的地址和JavaScript文件的地址,该JavaScript文件稍后会运行在你的服务器中。

接在在AppDelegate中添加如下方法:


func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)

// 1
let appControllerContext = TVApplicationControllerContext()

// 2
guard let javaScriptURL = NSURL(string: AppDelegate.TVBootURL) else {
fatalError("unable to create NSURL")
}
appControllerContext.javaScriptApplicationURL = javaScriptURL
appControllerContext.launchOptions["BASEURL"] = AppDelegate.TVBaseURL

// 3
appController = TVApplicationController(context: appControllerContext, window: window, delegate: self)
return true
}

这些代码相对还是比较容易理解的:

  1. 这里你首先创建了一个应用上下文TVApplicationControllerContext的实例,用于稍后初始化你的TVApplicationController。你可以理解为给一个简单的对象设置了一些属性,比如服务器的URL,然后该对象又作为属性设置给了另一个对象。
  2. 给应用上下文这个对象实例设置了两个简单的属性:主JavaScript文件的路径和服务器的地址。
  3. 通过你刚才设置好的应用上下文初始化TVApplicationController。此时就完全由Apple代码来接管了,他会加载到你的主JavaScript文件,并开始执行其内容。

所以到目前为止,是时候让Xcode休息一会了,因为接下来你们将要编写JavaScript了。

The JavaScript

在客户端-服务端这类的tvOS应用中,你的JavaScript文件通常在应用连接的服务器中。在该教程中,你们将会在Mac上搭建一个简单的服务器。

客户端代码

为了方便起见,我们把JavaScript文件放在桌面,在你们的 桌面 文件夹中新建一个文件夹名为 client 。在client文件夹中再新建一个文件夹名为 js 。该文件夹将作为你的JavaScript文件的容器。

通过你使用的编辑JavaScript的IDE,新建一个JavaScript文件,名为 application.js ,将它保存在你刚才新建的 js 文件夹中。然后在 application.js 中添加如下代码:


App.onLaunch = function(options) {
// 1
var alert = createAlert("Hello World", ""); //第二个参数传入空字符串
navigationDocument.presentModal(alert);
}

// 2
var createAlert = function(title, description) {
var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<alertTemplate>
<title>${title}</title>
<description>${description}</description>
</alertTemplate>
</document>`
var parser = new DOMParser();
var alertDoc = parser.parseFromString(alertString, "application/xml");
return alertDoc
}

App.onLaunch是处理JavaScript文件的入口方法。之前在 AppDelegate.swift 中已经初始化好的TVApplicationController会将TVApplicationControllerContext传到这。之后你会使用到上下文中的内容,但是现在,我们只创建一个简单的提示界面并显示在屏幕上。

  1. 通过下面定义的createAlert函数,我们获得到了为我们展现界面的TVML文件。navigationDocument类似于iOS中的UINavigationController,它提供像栈一样的方式,可以推出或压进展现界面的TVML文件。
  2. createAlert是一个返回TVML文件的函数,你可以将它看作类似iOS中的UIAlertController

写到这顺便提一下,Apple已经提供了18种TVML模板供我们使用,你们可以在该Apple TV Markup Language Reference中查阅完成的模板列表。

上述代码中的 alertTemplate 就是这18个模板中的其中一个,它的主要用于展示重要信息,比如通过一段消息提示用户在继续操作之前需要执行其他的操作等。此时,距离你们编译运行你们的第一个tvOS应用已为时不远了。

配置服务器

打开 Terminal 输入如下命令:


cd ~/Desktop/client
python -m SimpleHTTPServer 9001

这两行命令的作用是在先前创建的client目录中开启一个基于Python的web服务器。现在,你们可以准备起飞了!

回到你的Xcode项目中编译运行程序。你应该可以看到你的第一个tvOS TVML应用了!

tvOS-7

我不知道你们的感觉如何,但是当我第一次运行成功后,我的感受就像下面这个家伙一样:

tvOS-8

在继续进行教程之前,我想花点时间对你们目前已经完成的工作作以总结:

  1. 你们创建了TVApplicationController实例。它用于管理JavaScript代码。
  2. 你们创建了TVApplicationControllerContext实例,并在创建TVApplicationController时将其与之关联。应用上下文有一个launchOption属性,用来构建我们的BASEURL,也就是服务器的地址。该应用上下文也用于配置tvOS应用与哪个服务器连接。
  3. 控制器被传到了JavaScript代码中。App.onLaunch作为整个JavaScript文件的入口方法,你们定义了createAlert函数,返回TVML提示信息模板文件,并由navigationDocument管理并展现界面。最后将“Hello World”显示在屏幕上。

即使现在你们使用的服务器是运行在本机的,但是你们仍然可以连接一个真实的远程的服务器,可能是一个连着数据库的服务器。你们感受并想象一下应用场景,应该会很酷,对吧?

完善TVML模板

我之前提到过,createAlert是一个返回TVML模板文件的函数。有很多属性可由我们在TVML文件中编辑修改,作为一个实验性质的小例子,你们将会在当前的 alertTemplate 中添加一个按钮。回到你们的JavaScript代码中,将目光聚焦在createAlert函数上,在模板中添加一个按钮:


var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<alertTemplate>
<title>${title}</title>
<description>${description}</description>
<button>
<text>OK</text>
</button>
</alertTemplate>
</document>`

这里解释一下上述代码:

  1. 一个TVML文件的第一级标签是<document>,也就是整个模板内容是由<document></document>包起来的。
  2. 接着你们开始定义模板。使用Apple提供的 alertTemplate 模板,通过createAlert函数将其返回。
  3. 在该模板里,根据Apple的Apple TV Markup Language Reference文档规范,添加了按钮、标题、描述三个标签。

保存你们刚才编辑的JavaScript文件,再次编译运行。你们看到在提示信息下面出现了一个按钮。瞧,TVML是不是将构建tvOS UI变得很简单!

tvOS-9

注意:在一个模板中,你能添加的元素数量和类型基于这个的模板的类型。比如,一个 loading Template 就不允许有任何按钮出现。此外,你可以自定义字体、颜色和其他一些属性。但是这些知识已经超越了该教程的范畴。你们可以查阅Apple TV Markup Language Reference文档去了解更多TVML模板的信息。

丰富JavaScript客户端

到目前为止,你们已经完成了一些工作,并且正按照我们的指引一步一步达成目标。在这一节中,你们将要花一点时间在不同的JavaScript文件中将一些逻辑抽象出来,便于能更好的重用。

client/js 文件夹中新建一个JavaScript文件,名为 Presenter.js 。在该文件中,你们将定义Presenter类用于处理导航各个界面,或者说各个TVML模板文件,并且处理事件响应。在 Presenter.js 中添加如下代码:


var Presenter = {
// 1
makeDocument: function(resource) {
if (!Presenter.parser) {
Presenter.parser = new DOMParser();
}
var doc = Presenter.parser.parseFromString(resource, "application/xml");
return doc;
},
// 2
modalDialogPresenter: function(xml) {
navigationDocument.presentModal(xml);
},

// 3
pushDocument: function(xml) {
navigationDocument.pushDocument(xml);
},
}

让我们解释一下上述代码:

  1. 还记得你们在之前createAlert函数中用过的DOMParser类么,它可以将TVML字符串转换为可用于展示的TVML模板对象。因为该类不需要多次创建实例,所以采用单例模式创建它。然后通过DOMParserparseFormString()方法将TVML字符串转为模板对象。
  2. modalDialogPresenter方法通过传入的TVML模板文件,将其模态的展现在屏幕上。
  3. pushDocument方法是在导航栈中推送一个TVML模板文件,相当于在iOS中push出一个界面。

在之后,你们还会用到Presenter类管理选中处理操作。现在,让我们使用Presenter类对之前的JavaScript代码进行重构。将App.onLaunch中的代码替换为如下代码:


App.onLaunch = function(options) {
// 1
var javascriptFiles = [
`${options.BASEURL}js/Presenter.js`
];
// 2
evaluateScripts(javascriptFiles, function(success) {
if(success) {
var alert = createAlert("Hello World!", "");
Presenter.modalDialogPresenter(alert);
} else {
// 3 Handle the error CHALLENGE!//inside else statement of evaluateScripts.
}
});
}

这些代码相对还是比较容易理解的,我们来看一下:

  1. 首先创建一个新的JavaScript文件的数组。然后通过options参数获取到BASEURL属性,并组装Presenter.js的路径。这里的options就是之前我们在AppDelegate类中创建的TVApplicationControllerContextBASEURL自然也是那时我们设置的。
  2. evaluateScripts将加载JavaScript文件。
  3. 这里,你应该处理异常信息,稍后我们完善这里。

在继续进行之前,编译运行程序,确保JavaScript文件修改过之后程序仍能正常运行。此时,我们通过Presenter类对JavaScript代码的重构有了一个良好的开端:

tvOS-10

现在,看看上面代码中被注释的那行,你们能否自行完成对异常处理的挑战呢。如果evaluateScripts处理失败,可能是因为JavaScript文件的路径写错了,那么你可能会希望在此时显示一个提示消息给用户。提示: 之所以在这里出现了异常,是因为Presenter类加载失败导致,所以在这里你不能使用Presenter类显示提示信息的界面。

你应该通过之前所学到的知识来解决该问题。如果你们觉得有困难,那么可以参照下面的代码:


//将这两行代码插入evaluateScripts的else代码块中.
var errorDoc = createAlert("Evaluate Scripts Error", "Error attempting to evaluate external JavaScript files.");
navigationDocument.presentModal(errorDoc);

想要测试错误信息,你们可以修改一下JavaScript文件的路径,比如把Presenter.js改为Presentr.js


${options.BASEURL}js/Presentr.js

使用CatalogTemplate

catalogTemplate模板同样也是Apple提供的18个模板中的一个。它的作用是以分组的形式展现内容,用它来展示你们最喜欢的RWDevCon视频最好不过了! catalogTemplate有许多有意思的元素:

tvOS-11

复合元素和简单元素

该模板中的banner元素在应用顶部,用于展示应用基本信息,比如名称、标题等。它本身是一个 复合元素 ,也就是说它是由多个 简单元素 组合而成。比如,在banner中很显然有标题,那么该标题就是一个简单的title元素,并且在title背后还有背景图片,这又是另外一个简单元素background。所以banner是由两个简单元素组合而成。

让我们来试试这个模板吧。打开 client 文件夹,在 js 文件夹的同级目录新建两个文件夹,分别命名为 imagestemplates 。此时你的 client 文件夹里的内容应该是这样的:

tvOS-12

你们会需要图片构建模板中的Cells,在我们这个场景中就是一个一个的视频,图片自然就是视频的封面了。我已经为你们准备好了封面图片,你们可以从这里下载。下载成功后,将他们解压放在刚才你们创建的 images 文件夹中。

现在,你们即将要做的工作是在屏幕中显示图片!新建一个JavaScript文件,命名为 RWDevConTemplate.xml.js ,将其存在 templates 文件夹中。

打开 RWDevConTemplate.xml.js ,添加如下代码:


var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<catalogTemplate>
<banner>
<title>RWDevConHighlights</title>
</banner>
</catalogTemplate>
</document>`
}

现在,我们试图通过catalogTemplate模板显示一个Banner条。但在使用只包含模板信息的JavaScript文件之前,我们需要通过某种方法让其他的JavaScript文件知道该文件的存在并能加载其模板信息,因为当前它没有通过任何方式向其他JavaScript文件暴露过。所以我们要创建的最后一个JavaScript文件: ResourceLoader.js 就是用来解决该问题的!

ResourceLoader

新建一个JavaScript文件,命名为 ResourceLoader.js ,保存在 js 文件夹中,和 application.jsPresenter.js 一起。打开 ResourceLoader.js 添加如下代码:


function ResourceLoader(baseurl) {
this.BASEURL = baseurl;
}

ResourceLoader.prototype.loadResource = function(resource, callback) {
var self = this;
evaluateScripts([resource], function(success) {
if(success) {
var resource = Template.call(self);
callback.call(self, resource);
} else {
var title = "Resource Loader Error",
description = `Error loading resource '${resource}'. \n\n Try again later.`,
alert = createAlert(title, description);
navigationDocument.presentModal(alert);
}
});
}

不用过于担心看不懂这些代码逐行的含义,你们只要清楚这些代码的作用是加载其他模板文件就可以了。

之前我们的主屏显示的是“Hello World”的提示信息模板,现在试着将它换成我们创建的RWDevConTemplate。打开 application.js 文件,根据如下代码修改之前的代码:


// 1
var resourceLoader;

App.onLaunch = function(options) {
// 2
var javascriptFiles = [
`${options.BASEURL}js/ResourceLoader.js`,
`${options.BASEURL}js/Presenter.js`
];

evaluateScripts(javascriptFiles, function(success) {
if(success) {
// 3
resourceLoader = new ResourceLoader(options.BASEURL);
resourceLoader.loadResource(`${options.BASEURL}templates/RWDevConTemplate.xml.js`, function(resource) {
var doc = Presenter.makeDocument(resource);
Presenter.pushDocument(doc);
});
} else {
var errorDoc = createAlert("Evaluate Scripts Error", "Error attempting to evaluate external JavaScript files.");
navigationDocument.presentModal(errorDoc);
}
});
}

// 先不管createAlert函数

此时你们已经对之前的代码进行了三处的修改:

  1. 申明了一个resourceLoader变量。
  2. ResourceLoader.js 文件添加到JavaScript文件数组中。
  3. 使用resourceLoader加载TVML模板,然后使用Presenter展现在屏幕上。

编译运行程序,你们应该会看到如下界面:

tvOS-13

恭喜你们,现在你们已经可以通过更好的方式从JavaScript文件中加载TVML模板信息了,而不再使用硬编码写死在代码里!

完善catalogTemplate

你管你们信不信,我们要做的tvOS应用马上要接近尾声了。通过TVML开发tvOS应用最优雅的一件事就是添加界面元素非常之简单。

打开 RWDevConTemplate.xml.js 文件,按照如下代码更新之前代码:


var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<catalogTemplate>
<banner>
<title>RWDevConHighlights</title>
</banner>
//add stuff here
//1.
<list>
<section>
//2.
<listItemLockup>
<title>Inspiration Videos</title>
<decorationLabel>13</decorationLabel>
</listItemLockup>
</section>
</list>
</catalogTemplate>
</document>`
}
  1. 在上面的代码中,新定义了一个list标签,该标签中的内容就是显示在屏幕上除了Banner以外的全部内容。
  2. listItemLockup代表一个组,它以listItemLockup标签开头。在该标签中,通过title标签定义了它的名称“Inspiration Videos”,然后通过decorationLabel标签定义了该组中包含内容的数量。

编译运行程序,在模拟器中你们会看到如下界面:

tvOS-14

看着还不赖吧!

完成catalogTemplate

最后,我们准备在模板中添加cell,用于展示每一个视频。打开 RWDevConTemplate.xml.js 添加如下代码:


var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<catalogTemplate>
<banner>
<title>RWDevConHighlights</title>
</banner>
<list>
<section>
<listItemLockup>
<title>Inspiration Videos</title>
<decorationLabel>13</decorationLabel>
//1. add from here
<relatedContent>
<grid>
<section>
//2
<lockup videoURL="http://www.rwdevcon.com/videos/Ray-Wenderlich-Teamwork.mp4">
<img src="${this.BASEURL}images/ray.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Ryan-Nystrom-Contributing.mp4">
<img src="${this.BASEURL}images/ryan.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Matthijs-Hollemans-Math-Isnt-Scary.mp4">
<img src="${this.BASEURL}images/matthijs.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Vicki-Wenderlich-Identity.mp4">
<img src="${this.BASEURL}images/vicki.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Alexis-Gallagher-Identity.mp4">
<img src="${this.BASEURL}images/alexis.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Marin-Todorov-RW-Folklore.mp4">
<img src="${this.BASEURL}images/marin.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Chris-Wagner-Craftsmanship.mp4">
<img src="${this.BASEURL}images/chris.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Cesare-Rocchi-Cognition.mp4">
<img src="${this.BASEURL}images/cesare.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Ellen-Shapiro-Starting-Over.mp4">
<img src="${this.BASEURL}images/ellen.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Jake-Gundersen-Opportunity.mp4">
<img src="${this.BASEURL}images/jake.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Kim-Pedersen-Finishing.mp4">
<img src="${this.BASEURL}images/kim.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Tammy-Coron-Possible.mp4">
<img src="${this.BASEURL}images/tammy.png" width="500" height="308" />
</lockup>
<lockup videoURL="http://www.rwdevcon.com/videos/Saul-Mora-NSBrief.mp4">
<img src="${this.BASEURL}images/saul.png" width="500" height="308" />
</lockup>
</section>
</grid>
</relatedContent>
</listItemLockup>
</section>
</list>
</catalogTemplate>
</document>`
}
  1. 从上述代码中可以看到,在listItemLockup标签中添加了relatedContent,该标签是的作用是显示图中红色圆圈区域的:

tvOS-15

  1. 每个lockup代表一个视频,每个该标签中都有videoURL的属性,它的值就是 RWDevCon 网站上视频的地址。对于之后播放视频至关重要。

编译运行程序,会看到被你赋予新生命力的应用:

tvOS-16

现在,我们已经在“Inspiration Videos”这个组里添加了若干视频。让我们打开遥控器的模拟器,选中模拟器,在菜单栏中选择 Hardware\Show Apple TV Remote 。你可以通过遥控器中的 option 键选择不同的视频。

播放视频

到目前为止,我们已经构建好了应用的页面,看起来还是不错的。此时,你们可以再想想如果用iOS框架完成你们现在已经完成的布局,应该如何做。Apple把一些UI的细节全都抽象了出来,通过一个个模板提供给我们使用,可以让我们简单方便的通过模板创建出完美的界面,不得不说Apple做的太棒了。

接下来让我们完成最后两个遗留的功能:选择视频和播放视频。

选择事件

你们可能已经注意到了,当按下 enter 键或者在 Apple TV Remote 选择视频时并没有什么反应,所以是时候来实现选择视频的功能了。

打开Presenter,添加如下代码:


load: function(event) {
//1
var self = this,
ele = event.target,
videoURL = ele.getAttribute("videoURL")
if(videoURL) {
//2
var player = new Player();
var playlist = new Playlist();
var mediaItem = new MediaItem("video", videoURL);

player.playlist = playlist;
player.playlist.push(mediaItem);
player.present();
}
},
  1. load函数用来处理视频选择事件。它相当于iOS中的@IBAction,该函数的event参数相当于sender参数。每个event都有一个target,每个target关联着模板中的lockup元素。一个lockup代表应用中的一个视频,它里面有视频封面的属性,以及视频地址videoURL属性。
  2. 播放视频非常简单。PlayerTVJS 框架提供的一个类,负责所有视频播放的相关功能。你们所要做的只是添加一个播放列表playlist,然后将要播放的项目mediaItem添加到播放列表里。最后通过player.present()方法就可以播放视频了。

现在你们已经实现了选择视频后的响应事件。是时候将选择事件与每个视频关联在一起了。打开 application.js 文件,在App.onLaunch方法中添加如下代码:


App.onLaunch = function(options) {
//...
//在resourceLoader.loadResource中...
var doc = Presenter.makeDocument(resource);
doc.addEventListener("select", Presenter.load.bind(Presenter)); //add this line
Presenter.pushDocument(doc);
//...
}

上述代码中的addEventListener方法相当于iOS中按钮的@IBAction。编译运行程序,选择一个视频播放,你会看到一个完美的视频播放应用:

tvOS-17

大家可以在这里下载教程中的完整项目:clientRWDevCon

分享到: