Apple 于今年秋季发布了新版的 Apple TV,也带来了 iOS 开发者一直期盼的全新电视操作系统 — tvOS,正如 iPhone 的成功,Apple 从根本上就坚信基于应用的电视体验才是未来。tvOS 脱胎于 iOS,但又是一个完全独立的操作系统,拥有独立的 App Store。
官方提供了两种解决方案开发 tvOS 应用:
本文将介绍如何使用 TVML 和 TVJS 开发 一个 Client/Server App。
先介绍 SDK 的组成:
下图是 C/S App 的应用架构:
让我们开始吧~
进入工作目录,先初始化一个 Midway 项目:
midway init //选择经典的 `Midway(koa) + BDO + Render + Security` 即可
// ... 等待依赖安装完成
midway start // 启动应用
打开 http://localhost:6001/
,如果显示 Midway 欢迎页面就是说明 Server 环境 ok 啦。
我们需要做一些定制,主要用来请求远程数据和创建 TVML 模板。
tnpm i koa-jade koa-static --save
'use strict';
var midway = require('midway'),
koa = require('koa'),
serve = require('koa-static'),
Jade = require('koa-jade');
var app = midway(
koa()
);
// 使用 jade 模板引擎生成 xml 内容
var jade = new Jade({
viewPath: __dirname + '/app/views/',
debug: false,
noCache: true,
debug:true
})
app.use(jade.middleware);
// static,存储 app 需要的启动 JS
app.use(serve(__dirname + '/static'));
module.exports = app;
document
alertTemplate
title hello world
description first tvOS App with TVML and Midway
/* tvjs 启动文件 */
// app 启动回调
App.onLaunch = function() {
getDocument('http://localhost:6001/', function(error, doc) {
navigationDocument.pushDocument(doc);
});
}
// 获取 xml doc
function getDocument(url, callback) {
callback = callback || function() {};
var templateXHR = new XMLHttpRequest();
templateXHR.responseType = "document";
templateXHR.addEventListener("load", function() {
callback(null, templateXHR.responseXML);
}, false);
templateXHR.addEventListener("error", function(err) {
callback(err);
}, false);
templateXHR.open("GET", url, true);
templateXHR.send();
return templateXHR;
}
'use strict';
exports.index = function* () {
this.render('hello');
this.type = 'text/plain';
};
<document>
<alertTemplate>
<title>hello world</title>
<description>first tvOS App with TVML and Midway </description>
</alertTemplate>
</document>
好了,我们的服务端就搭建完成啦~
Main.storyboard
和 ViewController.swift
,选择 Move to Trash
Info.plist
,删除 Main storyboard file base name 这一配置Info.plist
里面添加配置。右击 Info.plist
,选择 open as => SourceCode
,在 dict child 中新增:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
import UIKit
import TVMLKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate ,TVApplicationControllerDelegate{
var window: UIWindow?
var appController: TVApplicationController?;
// 服务器地址
static let TVBootURL = "http://localhost:6001/main.js";
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// 创建 tvmlkit 环境
self.window = UIWindow(frame: UIScreen.mainScreen().bounds);
let appControllerContext = TVApplicationControllerContext();
if let javaScriptURL = NSURL(string: AppDelegate.TVBootURL) {
appControllerContext.javaScriptApplicationURL = javaScriptURL;
}
appController = TVApplicationController(context: appControllerContext, window: window, delegate: self);
return true
}
}
CTRL + R
启动 App,你会看到如下界面
App
: TVJS 提供的全局对象,用来管理应用生命周期,当应用启动的时候,会触发 App 的 onLaunch 事件navigationDocument
: NavigationDocument 实例,NavigationDocument 用来控制应用中的页面栈。应用生命周期中只有一个全局的 navigationDocument 实例我们了解了 C/S App 的大致工作原理后,就可以开发更复杂的应用啦。这里我们实现一个历届 WWDC 视频播放的 App。
'use strict';
// 模拟的 wwdc 数据
var videoData = [{
title: 'wwdc 2015',
desc:'wwdc 2015 年的学习视频',
data: [{
img: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/images/102_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}, {
img: 'http://devstreaming.apple.com/videos/wwdc/2015/105ncyldc6ofunvsgtan/105/images/105_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}, {
img: 'http://devstreaming.apple.com/videos/wwdc/2015/106z3yjwpfymnauri96m/106/images/106_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}, {
img: 'http://devstreaming.apple.com/videos/wwdc/2015/104usewvb5m0qbwafx8p/104/images/104_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}, {
img: 'http://devstreaming.apple.com/videos/wwdc/2015/709jcaer6su/709/images/709_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}, {
img: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/images/102_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}, {
img: 'http://devstreaming.apple.com/videos/wwdc/2015/212mm5ra3oau66/212/images/212_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}, {
img: 'http://devstreaming.apple.com/videos/wwdc/2015/106z3yjwpfymnauri96m/106/images/106_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}]
},{
title: 'wwdc 2014',
desc:'wwdc 2014 年的学习视频',
data: [{
img: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/images/102_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}]
},{
title: 'wwdc 2013',
desc:'wwdc 2013 年的学习视频',
data: [{
img: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/images/102_734x413.jpg',
title: 'Platforms State of the Union',
video: 'http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/hls_vod_mvp.m3u8'
}]
}];
exports.index = function*() {
this.render('list', {
data:videoData
});
this.type = 'text/plain';
};
<?xml version="1.0" encoding="UTF-8" ?>
document
catalogTemplate
banner
title 历届 wwdc 视频
list
section
each item in data
listItemLockup
title #{item.title}
decorationLabel
relatedContent
grid
section
each video in item.data
lockup(data-video-url="#{video.video}")
img(src="#{video.img}", width="350" , height="250")
title #{video.title}
接下来就可以继续实现播放视频功能了,首先让我们实现 cell 的点击事件。
在 main.js 中添加
App.onLaunch = function() {
getDocument('http://localhost:6001/', function(error, doc) {
navigationDocument.pushDocument(doc);
// 添加下面的内容
// 点击事件
doc.addEventListener('select', function(event) {
var ele = event.target;
// 获取视频地址
var videoURL = ele.getAttribute('data-video-url');
if (videoURL) {
var player = new Player();
var playlist = new Playlist();
var mediaItem = new MediaItem("video", videoURL);
player.playlist = playlist;
player.playlist.push(mediaItem);
player.present();
};
})
});
}
再次重启 Midway,重启应用,点击视频,哇咔咔,done
C/S App 技术 是 Apple 第一次在自己的类 iOS 系统中推出的 使用 web 技术开发 native 应用 的解决方案。我们也期待 Apple 早日将这一技术带入 iPhone 和 iPad,正如 React Native 一样,这样的技术将会给我们的业务开发带来巨大的变革。本文只是做了一些基础的讲解和开发框架探索,更深入的学习可以参考 Apple 的官方文档:
← Node.js 2015-12-04 漏洞浅析 让我们谈谈「生产环境中的 Node.js」- Node 地下铁第1次线下沙龙邀约 →