从 Apple TV Top Shelf 播放视频

Play video from Apple TV Top Shelf

我创建了一个简单的 AppleTV 项目来按类别显示多个视频,浏览和播放视频工作正常。它已使用 TVML 和 TVJS 作为客户端-服务器应用程序实现,因此大部分应用程序逻辑都在 Javascript 文件中。这些是定期从动态内容在后台静态生成的。

然后我向应用程序添加了一个 TopShelf 扩展程序,它从 API 中提取一些精选视频,这也工作正常,按预期提取视频。

我遇到的问题是检测并响应用户 select 从顶层视频播放视频。我创建了一个 URL scheme exampletvapp://,我已经在我的 plist 文件中注册了它。

我还向 TVContentItems 添加了显示URL 和播放URL。当 select 播放其中一个视频时,我的应用程序按预期启动但实际上并未处理该视频的播放。

在我的 AppDelegate 文件中,我添加了用于打印 launchOptions 的打印语句,还添加了一个 openUrl 函数,该函数确实会被调用,但仅在我关闭应用程序和 select 快速从顶层架子中获取一些内容时才会记录此函数在应用程序关闭之前我失去了调试会话。

// AppDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    print("App Launch: \(launchOptions)")
    window = UIWindow(frame: UIScreen.mainScreen().bounds)

    let appControllerContext = TVApplicationControllerContext()

    guard let javaScriptURL = NSURL(string: AppDelegate.TVBootURL) else {
        fatalError("unable to create NSURL")
    }

    appControllerContext.javaScriptApplicationURL = javaScriptURL
    appControllerContext.launchOptions["BASEURL"] = AppDelegate.TVBaseURL

    if let options = launchOptions {
        for (kind, value) in options {
            if let kindStr = kind as? String {
                appControllerContext.launchOptions[kindStr] = value
            }
        }
    }

    appController = TVApplicationController(context: appControllerContext, window: self.window, delegate: self)

    return true
}

    func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {

        guard let javaScriptURL = NSURL(string: AppDelegate.TVBootURL) else {
            fatalError("unable to create NSURL")
        }

        let appControllerContext = TVApplicationControllerContext()
        appControllerContext.javaScriptApplicationURL = javaScriptURL
        appControllerContext.launchOptions["BASEURL"] = AppDelegate.TVBaseURL


        for (kind, value) in options {
            appControllerContext.launchOptions[kind] = value
        }

        appController = TVApplicationController(context: appControllerContext, window: self.window, delegate: self)

        return true
    }

    func application(application: UIApplication, handleOpenURL url: NSURL) -> Bool {
        print("handle Url called")
    }

Javascript:

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

    console.log("options", options);

我在 JS 文件中添加了一个 console.log 来检查选项是否被传递,但它没有记录任何内容。我的意图是创建一个 Player 对象,其中包含来自 TopShelf 的 selected 视频的播放列表。但是由于 URL 似乎只传递了 ID,这意味着我需要发出 ajax 请求来获取视频信息,即使我已经在 TVContentItem 中获得了它。

所以我的问题是:我是应该将来自 topshelf 的视频本地添加到播放列表中并播放(使用 Swift)还是将详细信息传递到 javascript 端以供播放玩。如果是 javascript 是否有办法查看 console.log 的输出?因为我没有看到任何使它很难使用的东西。

更新:

我对此有更进一步的了解,我发现(通过 Apple 开发者论坛)您可以通过“开发”>“设备”菜单将 Safari 附加到 Apple TV 模拟器中的 JS 上下文。我已经这样做了,到目前为止 URL 没有通过选项传递给 JS。 url 被传递给 openURL 委托函数,该函数需要以某种方式传递给 JS,或者我需要本地播放视频。

我目前正在研究通过在显示器中传递所有必需的信息来本地播放视频URL 以创建 TVContentItem。我看不到从 openURL 方法将这个对象传递给 JS 的方法,除非我创建一个新的上下文和控制器实例(这甚至可能不起作用)

我终于让这个本地工作了。我也设法通过将 openUrl 传递给 JS 来让它工作,但它对我来说似乎不太可靠。我稍后会进一步研究,我现在只需要它工作。

下面的代码创建一个 AVPlayer 和 AVPlayerViewController 并显示 ViewController 并在完成后将其关闭。到目前为止,我还没有发现这种方法有任何问题,但我仍在测试中。

// AppDelegate.swift
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {

    let urlString = "http:\(url.path!)"
    let url = NSURL(string: urlString)!
    print("opening URL: \(url)")

    let item = AVPlayerItem(URL: url)

    // Need to listen to the end of the player so we can dismiss the AVPlayer
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification, object: item)

    let player = AVPlayer(playerItem: item)
    player.actionAtItemEnd = AVPlayerActionAtItemEnd.None
    let playerVC = AVPlayerViewController()
    playerVC.player = player

    appController?.navigationController.presentViewController(playerVC, animated: true, completion: nil)
    player.play()

    return true
}

func playerDidFinishPlaying (note: NSNotification) {
    // Dimiss the AVPlayer View Controller from the Navigation Controller
    appController?.navigationController.dismissViewControllerAnimated(true, completion: nil)
}

我也刚刚在我的 TVML-based 应用程序中添加了一个 Top Shelf 扩展。

如果您使用 Apple 的 TVML Catalog example as a base, then the following should work in the AppDelegate 将 URL 放入 JavaScript 上下文中:

func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {

    if url.host == nil { return true }

    let urlString = url.absoluteString

    NSLog(urlString) // Log to XCode

    appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in

        // Log to JS log (Safari)
        evaluation.evaluateScript("console.log('url string: \(urlString)')")

        // obviously we need to do something more interesting here


        },
        completion: {(Bool) -> Void in }

    )

    return true

}

我的计划是在 JavaScript 中编写一个函数来处理 URL 并将结果传递到我选择的 TVML 模板。我有一个用于现场表演,一个用于录音,这是我在 Top Shelf 扩展中展示的两个主要项目。