Разработка приложения для tvOS
На днях российские пользователи, заказавшие приставку Apple TV на официальном сайте, наконец начали получать долгожданные девайсы.
Как известно, в новом поколении приставок дебютирует магазин App Store с приложениями сторонних разработчиков. Первые российские обзоры с печалью отмечали, что в отечественном сторе пока не очень много приложений (и это надо как-то исправить!). Однако, среди них есть приложение Rutube. В этой статье мы поделимся небольшим опытом, который успели приобрести igor-petrov и Juraldinio за время его разработки.
О платформе tvOS
Платформа нового Apple TV (tvOS) является деривативом iOS и обладает некоторыми уникальными особенностями. В частности, для tvOS возможно как портирование существующего iOS-приложения, написанного на Objective-C / Swift (согласно рекомендациям по реализации интерфейса), так и создание приложения на новом стеке технологий — TVML и TVJS, подключаемых с помощью фреймворка TVMLKit. Этот новый стек позиционируется как хороший выбор для простой и быстрой разработки клиент-серверных приложений (идеально для видео!), что и подтверждается на практике. Именно о TVML и TVJS пойдет речь ниже.
TVMLKit
TVMLKit — фреймворк для Objective-C / Swift. С его помощью управление приложением передается JavaScript-файлу и среде TVML+TVJS. Подробности доступны на странице документации TVMLKit.
TVML
TVML (Television Markup Language) — это подмножество XML, реализующее разметку и стандартные элементы интерфейса tvOS. Этот интерфейс используется не только для приложений — на нем реализованы все страницы tvOS: домашний экран, экран настроек, и сам магазин App Store.
Например, так выглядит кнопка в TVML:
<button>
<text>Hello world!</text>
</button>
А так кнопка выглядит на экране:
А так выглядит домашний экран устройства, состоящий из элементов TVML:
tvOS предоставляет набор готовых элементов, полный список которых доступен в документации к TVML.
Эти элементы делятся на:
- Simple elements — простые элементы, состоящие из одного тега, например, text или title;
- Compound elements — составные элементы, которые могут включать в себя простые элементы. Например, составной элемент banner может содержать в себе простой элемент title;
- Templates — шаблоны отображения страницы. Задают общую структуру отображения элементов и могут включать в себя составные элементы.
Кроме того, элементам можно задавать атрибуты и стили, например width и src.
При этом есть ограничения — определенные типы составных элементов могут содержать лишь определенные типы простых элементов, то же касается и шаблонов. В целом, структура элементов показалась довольно запутанной — каждый раз приходилось заглядывать в справочник чтобы понять, где какой элемент можно использовать.
Из общего списка шаблонов стоит выделить divTemplate, который позволяет конструировать собственный layout, если вас не устроил никакой другой из предложенных.
Возможности создавать собственные элементы нет, можно только комбинировать существующие.
Более того, нужно следить за тем чтобы при парсинге строк в XML не попадался невалидный TVML. Например, ответ нашего API мог содержать HTML-теги, из-за чего возникала ошибка.
Разметка TVML может динамически добавляться на страницу с помощью JavaScript и TVJS.
TVJS
TVJS — JavaScript-фреймворк, предоставляющий API для работы с TVML. Условно, его можно разделить на две части:
- Стандартный DOM Level 3 API для работы с DOM-элементами, например, конструктор DOMParser для парсинга строки в XML-документ (TVML);
- Собственные методы и конструкторы tvOS, например, объект NavigationDocument, который управляет стеком отрисованных страниц и используется в качестве навигации по истории.
Каждый из собственных методов описан в документации к TVJS. В остальном это привычный JavaScript. Частично поддерживается ECMAScript 2015, например, классы и template strings.
Не поддерживаются arrow functions, константы. При этом может наблюдаться неработоспосбность некоторых сторонних библиотек. Например, пришлось перебрать несколько npm-пакетов, чтобы найти работающую библиотеку для AJAX-запросов (из-за обращений к window.XMLHttpRequest, который в tvOS реализован не как свойство window). Конечно, было бы проще самостоятельно написать несколько строк кода, но в какой-то момент это стало делом принципа — найти работающий из коробки плагин, и он существует!
Отладка
Для отладки JavaScript-кода можно использовать Safari. Активируем меню «Develop» (Preferences -> Advanced -> Show Develop menu), подключаем Apple TV через USB. Заходим в браузер и, запустив приложение на приставке, выбираем в появившемся выпадающем меню «Develop» девайс «Apple TV», далее выбираем наше приложение.
Hello world и запуск видеоролика
Далее небольшой пример того, как может выглядеть приложение для tvOS.
Для начала необходимо реализовать загрузку JavaScript-файла. Задача сводится к созданию объекта класса TVApplicationController, предоставляющего интерфейс для использования TVML:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// объект window
window = UIWindow(frame: UIScreen.mainScreen().bounds)
// контекст, необходимый для инициализации объекта класса TVApplicationController
let appContollerContext = TVApplicationControllerContext()
#if DEBUG
// для отладки можно использовать любой хост
let tvBaseUrl = "http://192.168.xx.xx/"
let tvBootUrl = "\(tvBaseUrl)js/main.js"
guard let javaScriptUrl = NSURL(string: tvBootUrl) else {
fatalError("Unable create js application url")
}
// ссылка на js-файл, которому необходимо передать управление
appContollerContext.javaScriptApplicationURL = javaScriptUrl
// указываем хост, используемый как BASE для подгрузки ресурсов
appContollerContext.launchOptions["BASEURL"] = tvBaseUrl;
#else
// в боевой версии js-файл подключается в bundle приложения
appContollerContext.javaScriptApplicationURL = NSBundle.mainBundle().URLForResource("application", withExtension:"js")!
#endif
// создаем объект и передаем управление js-файлу
appController = TVApplicationController(context:appContollerContext, window: window, delegate: self)
return true
}
После того, как управление перешло к JavaScript-файлу, можно приступать к реализации логики. Содержимое main.js:
App.onLaunch = function (options) {
console.log('Hello World!');
}
Здесь демонстрируется метод TVJS App. onLauch — входная точка приложения.
Попробуем сделать кое-что посложнее:
App.onLaunch = function () {
var alertString, alertXML, parser;
// текстовый шаблон TVML-документа
alertString = [
'<document>',
'<alertTemplate>',
'<title>Hello World!</title>',
'<button>',
'<text>OK</text>',
'</button>',
'</alertTemplate>',
'</document>'
].join('');
// создаем экземпляр DOMParser
parser = new DOMParser();
// парсим XML-документ из шаблона
alertXML = parser.parseFromString(alertString, 'application/xml');
// выводим XML-документ на страницу
navigationDocument.pushDocument(alertXML);
}
В этом примере демонстрируется конструктор DOMParser, с помощью которого строка преобразуется в XML-документ.
Используя метод TVJS navigationDocument.pushDocument(), можно отобразить получившийся документ на странице. При этом новая страница будет записана в массив navigationDocument.documents, который можно использовать для навигации по истории. В данном случае при запуске приложения на странице будет присутствовать надпись «Hello world» и кнопка «OK».
Перед вставкой XML-документа на страницу можно добавить обработчик пользовательского события:
...
// добавляем обработчик выбора элемента (клик на пульте)
alertXML.addEventListener('select', function () {
var player, mediaItem;
// создаем экземпляр MediaItem
mediaItem = new MediaItem('video', 'http://example.org/path/to.m3u8');
// создаем экземпляр плеера
player = new Player();
// создаем экземпляр Playlist
player.playlist = new Playlist();
// добавляем mediaItem в playlist
player.playlist.push(mediaItem);
// делаем плеер видимым
player.present();
// запускаем проигрывание
player.play();
});
...
В получившемся примере на элемент XML-документа — кнопку — вешается событие пользовательского выбора, а в коллбеке с помощью методов TVJS создается и запускается видеоплеер. Теперь при нажатии на кнопку «ОК» будет запускаться проигрывание видео.
App.onLaunch = function () {
var alertString, alertXML, parser;
// текстовый шаблон TVML-документа
alertString = [
'<document>',
'<alertTemplate>',
'<title>Hello World!</title>',
'<button>',
'<text>OK</text>',
'</button>',
'</alertTemplate>',
'</document>'
].join('');
// создаем экземпляр DOMParser
parser = new DOMParser();
// парсим XML-документ из шаблона
alertXML = parser.parseFromString(alertString, 'application/xml');
// добавляем обработчик выбора элемента (клик на пульте)
alertXML.addEventListener('select', function () {
var player, mediaItem;
// создаем экземпляр MediaItem
mediaItem = new MediaItem('video', 'http://example.org/path/to.m3u8');
// создаем экземпляр плеера
player = new Player();
// создаем экземпляр Playlist
player.playlist = new Playlist();
// добавляем mediaItem в свойство playlist плеера
player.playlist.push(mediaItem);
// делаем плеер видимым
player.present();
// запускаем проигрывание
player.play();
});
// выводим XML-документ на страницу
navigationDocument.pushDocument(alertXML);
};
Конечно, это совсем простой пример, но, он вполне отражает общий принцип работы с TVML и TVJS и показывает, что это решение — не какой-то Дивный Зверь, а близкая к классическому фронтэнду технология.
В заключение
В заключение пара слов о том, как на данный момент реализовано приложение Rutube.
Код написан на CoffeeScript, в качестве модульного загрузчика используется Browserify. Для получения данных используется XMLHttpRequest и открытый API Rutube. Для доставки видео используется протокол HLS.
В целом, разработка на TVML и TVJS понравилась — быстро и эффективно. Приложение хоть и создается в стандартизированном интерфейсе, но этот интерфейс выглядит весьма эффектно. При этом экономится время на верстке и создании визуальных эффектов, и не приходится задумываться об их производительности.
Из минусов хотелось бы отметить ограничения в дизайне. Ну и реализация поиска (текстового ввода) показалась неоднозначной. Даже не знаем, стоит ли его без Siri добавлять в приложение?
Приложение написано за несколько дней, но, если вдохновитесь и начнете разрабатывать свое — заложите в оценку некоторое время на освоение незнакомых API.
В будущем ожидается выход плагина для React, который пока (ноябрь 2015) находится в статусе «very alpha».
А пока надо отметить, что за прошедшую с релиза неделю, не напрягаясь, «за чашкой чая», добавили в наше приложение некоторое количество приятных глазу «фишечек» и готовим обновление.
Полезные ссылки
Документация по TVMLKit
Документация по TVJS
Документация по TVML
Beginning tvOS Development with TVML Tutorial