1. Обзор
Эта лаборатория кода научит вас, как изменить существующее видеоприложение iOS для трансляции контента на устройстве с поддержкой Google Cast .
Что такое Google Cast?
Google Cast позволяет пользователям транслировать контент с мобильного устройства на телевизор. Затем пользователи могут использовать свое мобильное устройство в качестве пульта дистанционного управления для воспроизведения мультимедиа на телевизоре.
Google Cast SDK позволяет расширить возможности вашего приложения для управления устройствами с поддержкой Google Cast (например, телевизором или аудиосистемой). Cast SDK позволяет добавлять необходимые компоненты пользовательского интерфейса на основе контрольного списка Google Cast Design Checklist .
Контрольный список Google Cast Design предназначен для того, чтобы сделать работу с Cast простой и предсказуемой на всех поддерживаемых платформах.
Что мы будем строить?
Когда вы завершите эту лабораторную работу, у вас будет видеоприложение для iOS, которое сможет транслировать видео на устройство Google Cast.
Что вы узнаете
- Как добавить SDK Google Cast в пример видеоприложения.
- Как добавить кнопку Cast для выбора устройства Google Cast.
- Как подключиться к Cast-устройству и запустить медиа-ресивер.
- Как залить видео.
- Как добавить мини-контроллер Cast в ваше приложение.
- Как добавить расширенный контроллер.
- Как сделать вводный оверлей.
- Как настроить виджеты Cast.
- Как интегрировать Cast Connect
Что вам понадобится
- Последний Xcode .
- Одно мобильное устройство с iOS 9 или более поздней версии (или Xcode Simulator).
- USB-кабель для передачи данных для подключения мобильного устройства к компьютеру разработчика (при использовании устройства).
- Устройство Google Cast, например Chromecast или Android TV, с доступом в Интернет.
- Телевизор или монитор с входом HDMI.
- Chromecast с Google TV требуется для тестирования интеграции Cast Connect, но не является обязательным для остальной части Codelab. Если у вас его нет, вы можете пропустить шаг добавления поддержки Cast Connect ближе к концу этого руководства.
Опыт
- Вам понадобятся предыдущие знания в области разработки iOS.
- Вам также понадобятся предварительные знания о просмотре телевизора :)
Как вы будете использовать этот учебник?
Как бы вы оценили свой опыт создания приложений для iOS?
Как бы вы оценили свой опыт просмотра телевизора?
2. Получите пример кода
Вы можете загрузить весь пример кода на свой компьютер...
и распакуйте загруженный zip-файл.
3. Запустите пример приложения
Во-первых, давайте посмотрим, как выглядит готовый образец приложения. Приложение представляет собой базовый видеоплеер. Пользователь может выбрать видео из списка, а затем воспроизвести видео локально на устройстве или транслировать его на устройство Google Cast.
После загрузки кода следующие инструкции описывают, как открыть и запустить завершенный пример приложения в Xcode:
Часто задаваемые вопросы
Настройка CocoaPods
Чтобы настроить CocoaPods, перейдите на свою консоль и установите Ruby по умолчанию, доступный в macOS:
sudo gem install cocoapods
Если у вас есть какие-либо проблемы, обратитесь к официальной документации , чтобы загрузить и установить менеджер зависимостей.
Настройка проекта
- Перейдите в свой терминал и перейдите в каталог codelab.
- Установите зависимости из подфайла.
cd app-done pod update pod install
- Откройте Xcode и выберите Открыть другой проект...
- Выберите файл
CastVideos-ios.xcworkspace
изкаталог
app-done
в папке примера кода.
Запустите приложение
Выберите цель и симулятор, а затем запустите приложение:
Через несколько секунд вы должны увидеть видеоприложение.
Обязательно нажмите «Разрешить», когда появится уведомление о принятии входящих сетевых подключений. Значок Cast не появится, если этот параметр не принят.
Нажмите кнопку Cast и выберите свое устройство Google Cast.
Выберите видео, нажмите на кнопку воспроизведения.
Видео начнет воспроизводиться на вашем устройстве Google Cast.
Отобразится расширенный контроллер. Вы можете использовать кнопку воспроизведения/паузы для управления воспроизведением.
Вернитесь к списку видео.
Мини-контроллер теперь виден внизу экрана.
Нажмите кнопку паузы на мини-контроллере, чтобы приостановить воспроизведение видео на приемнике. Нажмите кнопку воспроизведения на мини-контроллере, чтобы снова продолжить воспроизведение видео.
Нажмите кнопку Cast, чтобы остановить трансляцию на устройство Google Cast.
4. Подготовить стартовый проект
Нам нужно добавить поддержку Google Cast в загруженное вами начальное приложение. Вот некоторые термины Google Cast, которые мы будем использовать в этой кодовой лаборатории:
- приложение отправителя работает на мобильном устройстве или ноутбуке,
- приложение- приемник работает на устройстве Google Cast.
Настройка проекта
Теперь вы готовы строить поверх начального проекта с помощью Xcode:
- Перейдите в свой терминал и перейдите в каталог codelab.
- Установите зависимости из подфайла.
cd app-start pod update pod install
- Откройте Xcode и выберите Открыть другой проект...
- Выберите файл
CastVideos-ios.xcworkspace
изкаталог
app-start
в папке примера кода.
Дизайн приложения
Приложение получает список видео с удаленного веб-сервера и предоставляет пользователю список для просмотра. Пользователи могут выбрать видео, чтобы просмотреть подробности, или воспроизвести видео локально на мобильном устройстве.
Приложение состоит из двух основных контроллеров представления: MediaTableViewController
и MediaViewController.
Медиатаблевиевконтроллер
Этот UITableViewController отображает список видео из экземпляра MediaListModel
. Список видео и связанные с ними метаданные размещаются на удаленном сервере в виде файла JSON . MediaListModel
извлекает этот JSON и обрабатывает его для создания списка объектов MediaItem
.
Объект MediaItem
моделирует видео и связанные с ним метаданные, такие как заголовок, описание, URL-адрес изображения и URL-адрес потока.
MediaTableViewController
создает экземпляр MediaListModel
, а затем регистрируется как MediaListModelDelegate
, чтобы получать информацию о загрузке метаданных мультимедиа, чтобы он мог загрузить табличное представление.
Пользователю предоставляется список миниатюр видео с кратким описанием для каждого видео. Когда элемент выбран, соответствующий MediaItem
передается в MediaViewController
.
Медиевиевконтроллер
Этот контроллер представления отображает метаданные о конкретном видео и позволяет пользователю воспроизводить видео локально на мобильном устройстве.
Контроллер представления содержит LocalPlayerView
, некоторые элементы управления мультимедиа и текстовую область для отображения описания выбранного видео. Проигрыватель занимает верхнюю часть экрана, оставляя место для подробного описания видео внизу. Пользователь может воспроизводить/приостанавливать или искать локальное воспроизведение видео.
Часто задаваемые вопросы
5. Добавление кнопки Cast
Приложение с поддержкой Cast отображает кнопку Cast в каждом из своих контроллеров представления. При нажатии на кнопку Cast отображается список устройств Cast, которые может выбрать пользователь. Если пользователь воспроизводил контент локально на устройстве-отправителе, выбор устройства Cast запускает или возобновляет воспроизведение на этом устройстве Cast. В любой момент во время сеанса Cast пользователь может нажать кнопку Cast и прекратить трансляцию вашего приложения на устройство Cast. Пользователь должен иметь возможность подключаться к устройству Cast или отключаться от него, находясь на любом экране вашего приложения, как описано в Контрольном списке дизайна Google Cast .
Конфигурация
Для начального проекта требуются те же зависимости и настройка Xcode, что и для готового примера приложения. Вернитесь в этот раздел и выполните те же действия, чтобы добавить GoogleCast.framework
в стартовый проект приложения.
Инициализация
Фреймворк Cast имеет глобальный одноэлементный объект GCKCastContext
, который координирует все действия фреймворка. Этот объект должен быть инициализирован на ранней стадии жизненного цикла приложения, обычно в методе application(_:didFinishLaunchingWithOptions:)
делегата приложения, чтобы автоматическое возобновление сеанса при перезапуске приложения-отправителя могло запускаться правильно и сканирование устройств могло начаться.
Объект GCKCastOptions
должен быть предоставлен при инициализации GCKCastContext
. Этот класс содержит параметры, влияющие на поведение фреймворка. Наиболее важным из них является идентификатор приложения-приемника, который используется для фильтрации результатов обнаружения устройств Cast и для запуска приложения-приемника при запуске сеанса Cast.
Метод application(_:didFinishLaunchingWithOptions:)
также является хорошим местом для настройки делегата ведения журнала для получения сообщений ведения журнала от платформы Cast. Они могут быть полезны для отладки и устранения неполадок.
Когда вы разрабатываете собственное приложение с поддержкой Cast, вы должны зарегистрироваться в качестве разработчика Cast, а затем получить идентификатор приложения для своего приложения. Для этой кодовой лаборатории мы будем использовать пример идентификатора приложения.
Добавьте следующий код в AppDelegate.swift
, чтобы инициализировать GCKCastContext
с идентификатором приложения из пользовательских значений по умолчанию, и добавьте регистратор для платформы Google Cast:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
fileprivate var enableSDKLogging = true
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
window?.clipsToBounds = true
setupCastLogging()
...
}
...
func setupCastLogging() {
let logFilter = GCKLoggerFilter()
let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
"GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
GCKLogger.sharedInstance().filter = logFilter
GCKLogger.sharedInstance().delegate = self
}
}
...
// MARK: - GCKLoggerDelegate
extension AppDelegate: GCKLoggerDelegate {
func logMessage(_ message: String,
at _: GCKLoggerLevel,
fromFunction function: String,
location: String) {
if enableSDKLogging {
// Send SDK's log messages directly to the console.
print("\(location): \(function) - \(message)")
}
}
}
Кнопка трансляции
Теперь, когда GCKCastContext
инициализирован, нам нужно добавить кнопку Cast, чтобы пользователь мог выбрать устройство Cast. Cast SDK предоставляет компонент кнопки Cast с именем GCKUICastButton
в качестве подкласса UIButton
. Его можно добавить в строку заголовка приложения, обернув его в UIBarButtonItem
. Нам нужно добавить кнопку трансляции как в MediaTableViewController
, так и в MediaViewController
.
Добавьте следующий код в MediaTableViewController.swift
и MediaViewController.swift
:
import GoogleCast
@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
MediaListModelDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
print("MediaTableViewController - viewDidLoad")
super.viewDidLoad()
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Затем добавьте следующий код в свой MediaViewController.swift
:
import GoogleCast
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
LocalPlayerViewDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
super.viewDidLoad()
print("in MediaViewController viewDidLoad")
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Теперь запустите приложение. Вы должны увидеть кнопку Cast на панели навигации приложения, и когда вы нажмете на нее, она отобразит список устройств Cast в вашей локальной сети. Обнаружение устройства автоматически управляется GCKCastContext
. Выберите свое устройство Cast, и образец приложения-приемника загрузится на устройство Cast. Вы можете переключаться между активностью просмотра и активностью локального игрока, а состояние кнопки Cast синхронизируется.
Мы не подключили поддержку воспроизведения мультимедиа, поэтому вы пока не можете воспроизводить видео на устройстве Cast. Нажмите кнопку Cast, чтобы остановить трансляцию.
6. Трансляция видеоконтента
Мы расширим пример приложения, чтобы также удаленно воспроизводить видео на устройстве Cast. Для этого нам нужно прослушивать различные события, генерируемые фреймворком Cast.
Кастинг СМИ
На высоком уровне, если вы хотите воспроизвести медиафайл на устройстве Cast, должно произойти следующее:
- Создайте объект
GCKMediaInformation
из Cast SDK, который моделирует элемент мультимедиа. - Пользователь подключается к устройству Cast, чтобы запустить приложение-приемник.
- Загрузите объект
GCKMediaInformation
в свой ресивер и воспроизведите содержимое. - Отслеживайте статус носителя.
- Отправляйте команды воспроизведения на приемник на основе взаимодействия с пользователем.
Шаг 1 сводится к сопоставлению одного объекта с другим; GCKMediaInformation
— это то, что понимает Cast SDK, а MediaItem
— это инкапсуляция нашего приложения для элемента мультимедиа; мы можем легко сопоставить MediaItem
с GCKMediaInformation
. Мы уже сделали шаг 2 в предыдущем разделе. Шаг 3 легко выполнить с помощью Cast SDK.
Пример приложения MediaViewController
уже различает локальное и удаленное воспроизведение с помощью этого перечисления:
enum PlaybackMode: Int {
case none = 0
case local
case remote
}
private var playbackMode = PlaybackMode.none
В этой кодовой лаборатории не важно, чтобы вы точно понимали, как работает вся логика проигрывателя примеров. Важно понимать, что медиаплеер вашего приложения необходимо модифицировать, чтобы он одинаково узнавал о двух местах воспроизведения.
В настоящий момент локальный проигрыватель всегда находится в состоянии локального воспроизведения, так как он еще ничего не знает о состояниях трансляции. Нам нужно обновить пользовательский интерфейс на основе переходов состояний, которые происходят в среде Cast. Например, если мы начинаем кастинг, нам нужно остановить локальное воспроизведение и отключить некоторые элементы управления. Точно так же, если мы остановим кастинг, когда находимся в этом контроллере представления, нам нужно перейти к локальному воспроизведению. Чтобы справиться с этим, нам нужно прослушивать различные события, генерируемые платформой Cast.
Управление сессиями трансляции
Для платформы Cast сеанс Cast сочетает в себе этапы подключения к устройству, запуска (или присоединения), подключения к приложению-получателю и инициализации канала управления мультимедиа, если это необходимо. Канал управления мультимедиа — это то, как платформа Cast отправляет и получает сообщения от медиаплеера-получателя.
Сеанс трансляции будет запущен автоматически, когда пользователь выберет устройство с помощью кнопки трансляции, и будет автоматически остановлен, когда пользователь отключится. Повторное подключение к сеансу получателя из-за проблем с сетью также автоматически обрабатывается платформой Cast.
Сеансы трансляции управляются GCKSessionManager
, доступ к которому можно получить через GCKCastContext.sharedInstance().sessionManager
. Обратные вызовы GCKSessionManagerListener
можно использовать для отслеживания событий сеанса, таких как создание, приостановка, возобновление и завершение.
Сначала нам нужно зарегистрировать прослушиватель сеанса и инициализировать некоторые переменные:
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
private var sessionManager: GCKSessionManager!
...
required init?(coder: NSCoder) {
super.init(coder: coder)
sessionManager = GCKCastContext.sharedInstance().sessionManager
...
}
override func viewWillAppear(_ animated: Bool) {
...
let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
if hasConnectedSession, (playbackMode != .remote) {
populateMediaInfo(false, playPosition: 0)
switchToRemotePlayback()
} else if sessionManager.currentSession == nil, (playbackMode != .local) {
switchToLocalPlayback()
}
sessionManager.add(self)
...
}
override func viewWillDisappear(_ animated: Bool) {
...
sessionManager.remove(self)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
super.viewWillDisappear(animated)
}
func switchToLocalPlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
}
func switchToRemotePlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
...
}
// MARK: - GCKSessionManagerListener
func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
print("MediaViewController: sessionManager didStartSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
print("MediaViewController: sessionManager didResumeSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
print("session ended with error: \(String(describing: error))")
let message = "The Casting session has ended.\n\(String(describing: error))"
if let window = appDelegate?.window {
Toast.displayMessage(message, for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
if let error = error {
showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
}
setQueueButtonVisible(false)
}
func sessionManager(_: GCKSessionManager,
didFailToResumeSession _: GCKSession, withError _: Error?) {
if let window = UIApplication.shared.delegate?.window {
Toast.displayMessage("The Casting session could not be resumed.",
for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
...
}
В MediaViewController
нам нужно получать информацию о том, когда мы подключаемся или отключаемся от устройства Cast, чтобы мы могли переключаться на локальный проигрыватель или с него. Обратите внимание, что соединение может быть нарушено не только экземпляром вашего приложения, работающим на вашем мобильном устройстве, но также может быть нарушено другим экземпляром вашего (или другого) приложения, работающим на другом мобильном устройстве.
Текущий активный сеанс доступен как GCKCastContext.sharedInstance().sessionManager.currentCastSession
. Сеансы создаются и закрываются автоматически в ответ на действия пользователя в диалоговых окнах Cast.
Загрузка носителя
В Cast SDK GCKRemoteMediaClient
предоставляет набор удобных API для управления удаленным воспроизведением мультимедиа на приемнике. Для сеанса GCKCastSession
, который поддерживает воспроизведение мультимедиа, экземпляр GCKRemoteMediaClient
будет автоматически создан пакетом SDK. Доступ к нему можно получить как свойство remoteMediaClient
экземпляра GCKCastSession
.
Добавьте следующий код в MediaViewController.swift
, чтобы загрузить текущее выбранное видео в приемник:
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
@objc func playSelectedItemRemotely() {
loadSelectedItem(byAppending: false)
}
/**
* Loads the currently selected item in the current cast media session.
* @param appending If YES, the item is appended to the current queue if there
* is one. If NO, or if
* there is no queue, a new queue containing only the selected item is created.
*/
func loadSelectedItem(byAppending appending: Bool) {
print("enqueue item \(String(describing: mediaInfo))")
if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = true
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
let mediaQueueItem = mediaQueueItemBuilder.build()
if appending {
let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
request.delegate = self
} else {
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
request.delegate = self
}
}
}
...
}
Теперь обновите различные существующие методы, чтобы использовать логику сеанса трансляции для поддержки удаленного воспроизведения:
required init?(coder: NSCoder) {
super.init(coder: coder)
...
castMediaController = GCKUIMediaController()
...
}
func switchToLocalPlayback() {
print("switchToLocalPlayback")
if playbackMode == .local {
return
}
setQueueButtonVisible(false)
var playPosition: TimeInterval = 0
var paused: Bool = false
var ended: Bool = false
if playbackMode == .remote {
playPosition = castMediaController.lastKnownStreamPosition
paused = (castMediaController.lastKnownPlayerState == .paused)
ended = (castMediaController.lastKnownPlayerState == .idle)
print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
}
populateMediaInfo((!paused && !ended), playPosition: playPosition)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
playbackMode = .local
}
func switchToRemotePlayback() {
print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
if playbackMode == .remote {
return
}
// If we were playing locally, load the local media on the remote player
if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
print("loading media: \(String(describing: mediaInfo))")
let paused: Bool = (_localPlayerView.playerState == .paused)
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = !paused
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
let mediaQueueItem = mediaQueueItemBuilder.build()
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
request?.delegate = self
}
_localPlayerView.stop()
_localPlayerView.showSplashScreen()
setQueueButtonVisible(true)
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
playbackMode = .remote
}
/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
let hasConnectedCastSession = sessionManager.hasConnectedCastSession
if mediaInfo != nil, hasConnectedCastSession() {
// Display an alert box to allow the user to add to queue or play
// immediately.
if actionSheet == nil {
actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
actionSheet?.addAction(withTitle: "Play Now", target: self,
selector: #selector(playSelectedItemRemotely))
}
actionSheet?.present(in: self, sourceView: _localPlayerView)
return false
}
return true
}
Теперь запустите приложение на своем мобильном устройстве. Подключитесь к устройству Cast и начните воспроизведение видео. Вы должны увидеть видео, воспроизводимое на приемнике.
7. Мини-контроллер
Контрольный список Cast Design требует, чтобы все приложения Cast предоставляли мини-контроллер , который появляется, когда пользователь уходит с текущей страницы содержимого. Мини-контроллер обеспечивает мгновенный доступ и визуальное напоминание о текущем сеансе Cast.
Cast SDK предоставляет панель управления GCKUIMiniMediaControlsViewController
, которую можно добавить к сценам, в которых вы хотите отображать постоянные элементы управления.
Для примера приложения мы собираемся использовать GCKUICastContainerViewController
, который обертывает другой контроллер представления и добавляет внизу GCKUIMiniMediaControlsViewController
.
Измените файл AppDelegate.swift
и добавьте следующий код для условия if useCastContainerViewController
в следующем методе:
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
as? UINavigationController else { return false }
let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
as GCKUICastContainerViewController
castContainerVC.miniMediaControlsItemEnabled = true
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = castContainerVC
window?.makeKeyAndVisible()
...
}
Добавьте это свойство и сеттер/геттер для управления видимостью мини-контроллера (мы будем использовать их в следующем разделе):
var isCastControlBarsEnabled: Bool {
get {
if useCastContainerViewController {
let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
return castContainerVC!.miniMediaControlsItemEnabled
} else {
let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
return rootContainerVC!.miniMediaControlsViewEnabled
}
}
set(notificationsEnabled) {
if useCastContainerViewController {
var castContainerVC: GCKUICastContainerViewController?
castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
} else {
var rootContainerVC: RootContainerViewController?
rootContainerVC = (window?.rootViewController as? RootContainerViewController)
rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
}
}
}
Запустите приложение и снимите видео. Когда воспроизведение начнется на ресивере, вы должны увидеть мини-контроллер внизу каждой сцены. Вы можете управлять дистанционным воспроизведением с помощью мини-контроллера. Если вы переключаетесь между активностью просмотра и активностью локального проигрывателя, состояние мини-контроллера должно оставаться синхронизированным со статусом воспроизведения мультимедиа на приемнике.
8. Вводный оверлей
Контрольный список дизайна Google Cast требует, чтобы приложение-отправитель представило кнопку Cast существующим пользователям, чтобы сообщить им, что приложение-отправитель теперь поддерживает Casting, а также помогает пользователям, не знакомым с Google Cast.
Класс GCKCastContext
имеет метод presentCastInstructionsViewControllerOnce
, который можно использовать для выделения кнопки Cast, когда она впервые отображается пользователям. Добавьте следующий код в MediaViewController.swift
и MediaTableViewController.swift
:
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
name: NSNotification.Name.gckCastStateDidChange,
object: GCKCastContext.sharedInstance())
}
@objc func castDeviceDidChange(_: Notification) {
if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
// You can present the instructions on how to use Google Cast on
// the first time the user uses you app
GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
}
}
Запустите приложение на своем мобильном устройстве, и вы должны увидеть вступительный оверлей.
9. Расширенный контроллер
Контрольный список дизайна Google Cast требует, чтобы приложение-отправитель предоставляло расширенный контроллер для транслируемого мультимедиа. Расширенный контроллер представляет собой полноэкранную версию мини-контроллера.
Расширенный контроллер представляет собой полноэкранный режим, обеспечивающий полный контроль над удаленным воспроизведением мультимедиа. Это представление должно позволять приложению трансляции управлять всеми управляемыми аспектами сеанса трансляции, за исключением управления громкостью приемника и жизненным циклом сеанса (подключение/остановка трансляции). Он также предоставляет всю информацию о состоянии сеанса мультимедиа (обложка, заголовок, подзаголовок и т. д.).
Функциональность этого представления реализуется классом GCKUIExpandedMediaControlsViewController
.
Первое, что вам нужно сделать, это включить расширенный контроллер по умолчанию в контексте приведения. Измените AppDelegate.swift
, чтобы включить расширенный контроллер по умолчанию:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
// Add after the setShareInstanceWith(options) is set.
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
...
}
...
}
Добавьте следующий код в MediaViewController.swift
, чтобы загружать расширенный контроллер, когда пользователь начинает транслировать видео:
@objc func playSelectedItemRemotely() {
...
appDelegate?.isCastControlBarsEnabled = false
GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}
Расширенный контроллер также будет запускаться автоматически, когда пользователь коснется мини-контроллера.
Запустите приложение и снимите видео. Вы должны увидеть расширенный контроллер. Вернитесь к списку видео, и когда вы нажмете на мини-контроллер, расширенный контроллер будет загружен снова.
10. Добавьте поддержку Cast Connect
Библиотека Cast Connect позволяет существующим приложениям-отправителям взаимодействовать с приложениями Android TV через протокол Cast. Cast Connect строится на основе инфраструктуры Cast, а ваше приложение Android TV выступает в роли приемника.
Зависимости
В вашем Podfile
убедитесь, что google-cast-sdk
указана 4.4.8
или выше, как указано ниже. Если вы внесли изменения в файл, запустите pod update
из консоли, чтобы синхронизировать изменения с вашим проектом.
pod 'google-cast-sdk', '>=4.4.8'
GCKLaunchOptions
Чтобы запустить приложение Android TV, также называемое Android Receiver, нам нужно установить для флага androidReceiverCompatible
значение true в объекте GCKLaunchOptions
. Этот объект GCKLaunchOptions
определяет, как запускается приемник и передается в GCKCastOptions
, которые устанавливаются в общем экземпляре с помощью GCKCastContext.setSharedInstanceWith
.
Добавьте следующие строки в ваш AppDelegate.swift
:
let options = GCKCastOptions(discoveryCriteria:
GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
Установить учетные данные для запуска
На стороне отправителя вы можете указать GCKCredentialsData
, чтобы представить, кто присоединяется к сеансу. credentials
— это строка, которая может быть определена пользователем, если ваше приложение ATV может ее понять. GCKCredentialsData
передается вашему приложению Android TV только во время запуска или присоединения. Если вы установите его снова, когда вы подключены, он не будет передан в ваше приложение Android TV.
Чтобы установить Launch Credentials, GCKCredentialsData
необходимо определить в любое время после установки GCKLaunchOptions
. Чтобы продемонстрировать это, давайте добавим логику для кнопки Creds , чтобы установить учетные данные, которые будут передаваться при установке сеанса. Добавьте следующий код в свой MediaTableViewController.swift
:
class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
...
private var credentials: String? = nil
...
override func viewDidLoad() {
...
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
target: self, action: #selector(toggleLaunchCreds))
...
setLaunchCreds()
}
...
@objc func toggleLaunchCreds(_: Any){
if (credentials == nil) {
credentials = "{\"userId\":\"id123\"}"
} else {
credentials = nil
}
Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
print("Credentials set: "+(credentials ?? "Null"))
setLaunchCreds()
}
...
func setLaunchCreds() {
GCKCastContext.sharedInstance()
.setLaunch(GCKCredentialsData(credentials: credentials))
}
}
Установить учетные данные при запросе загрузки
Чтобы обрабатывать credentials
как в веб-приложениях, так и в приложениях Android TV Receiver, добавьте следующий код в свой класс MediaTableViewController.swift
в функцию loadSelectedItem
:
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...
В зависимости от приложения-получателя, к которому транслируется ваш отправитель, SDK автоматически применит вышеуказанные учетные данные к текущему сеансу.
Тестирование Cast Connect
Действия по установке Android TV APK на Chromecast с Google TV
- Найдите IP-адрес вашего устройства Android TV. Обычно он доступен в разделе «Настройки» > «Сеть и Интернет» > (имя сети, к которой подключено ваше устройство) . Справа будут показаны подробности и IP-адрес вашего устройства в сети.
- Используйте IP-адрес вашего устройства, чтобы подключиться к нему через ADB с помощью терминала:
$ adb connect <device_ip_address>:5555
- В окне терминала перейдите в папку верхнего уровня для образцов кодовой лаборатории, которые вы загрузили в начале этой кодовой лаборатории. Например:
$ cd Desktop/ios_codelab_src
- Установите файл .apk из этой папки на свой Android TV, запустив:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- Теперь вы сможете увидеть приложение под названием Cast Videos в меню «Ваши приложения» на устройстве Android TV.
- После этого создайте и запустите приложение на эмуляторе или мобильном устройстве. При установлении сеанса трансляции с вашим устройством Android TV теперь должно запускаться приложение Android Receiver на вашем Android TV. Воспроизведение видео с вашего мобильного отправителя iOS должно запустить видео в приемнике Android и позволить вам управлять воспроизведением с помощью пульта дистанционного управления для вашего устройства Android TV.
11. Настройте виджеты Cast
Инициализация
Начните с папки App-Done. Добавьте следующее в метод applicationDidFinishLaunchingWithOptions
в файле AppDelegate.swift
.
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let styler = GCKUIStyle.sharedInstance()
...
}
Как только вы закончите применять одну или несколько настроек, как указано в остальной части этой кодовой лаборатории, зафиксируйте стили, вызвав код ниже.
styler.apply()
Настройка представлений Cast
Вы можете настроить все представления, которыми управляет Cast Application Framework, используя рекомендации по стилю по умолчанию для представлений. В качестве примера давайте изменим цвет оттенка значка.
styler.castViews.iconTintColor = .lightGray
При необходимости вы можете переопределить значения по умолчанию для каждого экрана. Например, чтобы переопределить lightGrayColor для цвета оттенка значка только для расширенного контроллера мультимедиа.
styler.castViews.mediaControl.expandedController.iconTintColor = .green
Изменение цвета
Вы можете настроить цвет фона для всех представлений (или индивидуально для каждого представления). Следующий код задает синий цвет фона для всех представлений, предоставляемых Cast Application Framework.
styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow
Изменение шрифтов
Вы можете настроить шрифты для различных меток, отображаемых в видах приведения. Давайте установим для всех шрифтов значение «Courier-Oblique» для наглядности.
styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)
Изменение изображений кнопок по умолчанию
Добавьте в проект свои собственные изображения и назначьте изображения кнопкам, чтобы стилизовать их.
let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
styler.castViews.muteOnImage = muteOnImage
}
Изменение темы кнопки Cast
Вы также можете создавать виджеты Cast, используя протокол UIAppearance. Следующий код оформляет GCKUICastButton во всех представлениях, в которых он появляется:
GCKUICastButton.appearance().tintColor = UIColor.gray
12. Поздравления
Теперь вы знаете, как включить Cast для видеоприложения с помощью виджетов Cast SDK на iOS.
Дополнительные сведения см. в руководстве разработчика iOS Sender .
1. Обзор
Эта лаборатория кода научит вас, как изменить существующее видеоприложение iOS для трансляции контента на устройстве с поддержкой Google Cast .
Что такое Google Cast?
Google Cast позволяет пользователям транслировать контент с мобильного устройства на телевизор. Затем пользователи могут использовать свое мобильное устройство в качестве пульта дистанционного управления для воспроизведения мультимедиа на телевизоре.
Google Cast SDK позволяет расширить возможности вашего приложения для управления устройствами с поддержкой Google Cast (например, телевизором или аудиосистемой). Cast SDK позволяет добавлять необходимые компоненты пользовательского интерфейса на основе контрольного списка Google Cast Design Checklist .
Контрольный список Google Cast Design предназначен для того, чтобы сделать работу с Cast простой и предсказуемой на всех поддерживаемых платформах.
Что мы будем строить?
Когда вы завершите эту лабораторную работу, у вас будет видеоприложение для iOS, которое сможет транслировать видео на устройство Google Cast.
Что вы узнаете
- Как добавить SDK Google Cast в пример видеоприложения.
- Как добавить кнопку Cast для выбора устройства Google Cast.
- Как подключиться к Cast-устройству и запустить медиа-ресивер.
- Как залить видео.
- Как добавить мини-контроллер Cast в ваше приложение.
- Как добавить расширенный контроллер.
- Как сделать вводный оверлей.
- Как настроить виджеты Cast.
- Как интегрировать Cast Connect
Что вам понадобится
- Последний Xcode .
- Одно мобильное устройство с iOS 9 или более поздней версии (или Xcode Simulator).
- USB-кабель для передачи данных для подключения мобильного устройства к компьютеру разработчика (при использовании устройства).
- Устройство Google Cast, например Chromecast или Android TV, с доступом в Интернет.
- Телевизор или монитор с входом HDMI.
- Chromecast с Google TV требуется для тестирования интеграции Cast Connect, но не является обязательным для остальной части Codelab. Если у вас его нет, вы можете пропустить шаг добавления поддержки Cast Connect ближе к концу этого руководства.
Опыт
- Вам понадобятся предыдущие знания в области разработки iOS.
- Вам также понадобятся предварительные знания о просмотре телевизора :)
Как вы будете использовать этот учебник?
Как бы вы оценили свой опыт создания приложений для iOS?
Как бы вы оценили свой опыт просмотра телевизора?
2. Получите пример кода
Вы можете загрузить весь пример кода на свой компьютер...
и распакуйте загруженный zip-файл.
3. Запустите пример приложения
Во-первых, давайте посмотрим, как выглядит готовый пример приложения. Приложение представляет собой базовый видеоплеер. Пользователь может выбрать видео из списка, а затем воспроизвести видео локально на устройстве или транслировать его на устройство Google Cast.
После загрузки кода следующие инструкции описывают, как открыть и запустить завершенный пример приложения в Xcode:
Часто задаваемые вопросы
Настройка CocoaPods
Чтобы настроить CocoaPods, перейдите на свою консоль и установите Ruby по умолчанию, доступный в macOS:
sudo gem install cocoapods
Если у вас есть какие-либо проблемы, обратитесь к официальной документации , чтобы загрузить и установить менеджер зависимостей.
Настройка проекта
- Перейдите в свой терминал и перейдите в каталог codelab.
- Установите зависимости из подфайла.
cd app-done pod update pod install
- Откройте Xcode и выберите Открыть другой проект...
- Выберите файл
CastVideos-ios.xcworkspace
изкаталог
app-done
в папке примера кода.
Запустите приложение
Выберите цель и симулятор, а затем запустите приложение:
Через несколько секунд вы должны увидеть видеоприложение.
Обязательно нажмите «Разрешить», когда появится уведомление о принятии входящих сетевых подключений. Значок Cast не появится, если этот параметр не принят.
Нажмите кнопку Cast и выберите свое устройство Google Cast.
Выберите видео, нажмите на кнопку воспроизведения.
Видео начнет воспроизводиться на вашем устройстве Google Cast.
Отобразится расширенный контроллер. Вы можете использовать кнопку воспроизведения/паузы для управления воспроизведением.
Вернитесь к списку видео.
Мини-контроллер теперь виден внизу экрана.
Нажмите кнопку паузы на мини-контроллере, чтобы приостановить воспроизведение видео на приемнике. Нажмите кнопку воспроизведения на мини-контроллере, чтобы снова продолжить воспроизведение видео.
Нажмите кнопку Cast, чтобы остановить трансляцию на устройство Google Cast.
4. Подготовить стартовый проект
Нам нужно добавить поддержку Google Cast в загруженное вами начальное приложение. Вот некоторые термины Google Cast, которые мы будем использовать в этой кодовой лаборатории:
- приложение отправителя работает на мобильном устройстве или ноутбуке,
- приложение- приемник работает на устройстве Google Cast.
Настройка проекта
Теперь вы готовы строить поверх начального проекта с помощью Xcode:
- Перейдите в свой терминал и перейдите в каталог codelab.
- Установите зависимости из подфайла.
cd app-start pod update pod install
- Откройте Xcode и выберите Открыть другой проект...
- Выберите файл
CastVideos-ios.xcworkspace
изкаталог
app-start
в папке примера кода.
Дизайн приложения
Приложение получает список видео с удаленного веб-сервера и предоставляет пользователю список для просмотра. Пользователи могут выбрать видео, чтобы просмотреть подробности, или воспроизвести видео локально на мобильном устройстве.
Приложение состоит из двух основных контроллеров представления: MediaTableViewController
и MediaViewController.
Медиатаблевиевконтроллер
Этот UITableViewController отображает список видео из экземпляра MediaListModel
. The list of videos and their associated metadata are hosted on a remote server as a JSON file. MediaListModel
fetches this JSON and processes it to build a list of MediaItem
objects.
A MediaItem
object models a video and its associated metadata, such as its title, description, URL for an image, and URL for the stream.
MediaTableViewController
creates a MediaListModel
instance and then registers itself as a MediaListModelDelegate
to be informed when the media metadata has been downloaded so it can load the table view.
The user is presented with a list of video thumbnails with a short description for each video. When an item is selected, the corresponding MediaItem
is passed to the MediaViewController
.
MediaViewController
This view controller displays the metadata about a particular video and allows the user to play the video locally on the mobile device.
The view controller hosts a LocalPlayerView
, some media controls, and a text area to show the description of the selected video. The player covers the top portion of the screen, leaving room for the detailed description of the video beneath The user can play/pause or seek the local video playback.
Frequently asked questions
5. Adding the Cast button
A Cast-enabled application displays the Cast button in each of its view controllers. Clicking on the Cast button displays a list of Cast devices which a user can select. If the user was playing content locally on the sender device, selecting a Cast device starts or resumes playback on that Cast device. At any time during a Cast session, the user can click on the Cast button and stop casting your application to the Cast device. The user must be able to connect to or disconnect from the Cast device while in any screen of your application, as described in the Google Cast Design Checklist .
Configuration
The start project requires the same dependencies and Xcode setup as you did for the completed sample app. Return to that section and follow the same steps to add the GoogleCast.framework
to the start app project.
Initialization
The Cast framework has a global singleton object, the GCKCastContext
, which coordinates all of the framework's activities. This object must be initialized early in the application's lifecycle, typically in the application(_:didFinishLaunchingWithOptions:)
method of the app delegate, so that automatic session resumption on the sender application restart can trigger properly and scanning for devices can start.
A GCKCastOptions
object must be supplied when initializing the GCKCastContext
. This class contains options that affect the behavior of the framework. The most important of these is the receiver application ID, which is used to filter Cast device discovery results and to launch the receiver application when a Cast session is started.
The application(_:didFinishLaunchingWithOptions:)
method is also a good place to set up a logging delegate to receive the logging messages from Cast framework. These can be useful for debugging and troubleshooting.
When you develop your own Cast-enabled app, you have to register as a Cast developer and then obtain an application ID for your app. For this codelab, we will be using a sample app ID.
Add the following code to AppDelegate.swift
to initialize GCKCastContext
with the application ID from the user defaults, and add a logger for the Google Cast framework:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
fileprivate var enableSDKLogging = true
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
window?.clipsToBounds = true
setupCastLogging()
...
}
...
func setupCastLogging() {
let logFilter = GCKLoggerFilter()
let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
"GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
GCKLogger.sharedInstance().filter = logFilter
GCKLogger.sharedInstance().delegate = self
}
}
...
// MARK: - GCKLoggerDelegate
extension AppDelegate: GCKLoggerDelegate {
func logMessage(_ message: String,
at _: GCKLoggerLevel,
fromFunction function: String,
location: String) {
if enableSDKLogging {
// Send SDK's log messages directly to the console.
print("\(location): \(function) - \(message)")
}
}
}
Cast button
Now that the GCKCastContext
is initialized, we need to add the Cast button to allow the user to select a Cast device. The Cast SDK provides a Cast button component called GCKUICastButton
as a UIButton
subclass. It can be added to the application's title bar by wrapping it in a UIBarButtonItem
. We need to add the Cast button to both the MediaTableViewController
and the MediaViewController
.
Add the following code to MediaTableViewController.swift
and MediaViewController.swift
:
import GoogleCast
@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
MediaListModelDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
print("MediaTableViewController - viewDidLoad")
super.viewDidLoad()
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Next, add the following code to your MediaViewController.swift
:
import GoogleCast
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
LocalPlayerViewDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
super.viewDidLoad()
print("in MediaViewController viewDidLoad")
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Now run the app. You should see a Cast button in the app's navigation bar and when you click on it, it will list the Cast devices on your local network. Device discovery is managed automatically by the GCKCastContext
. Select your Cast device and the sample receiver app will load on the Cast device. You can navigate between the browse activity and the local player activity and the Cast button state is kept in sync.
We haven't hooked up any support for media playback, so you can't play videos on the Cast device yet. Click on the Cast button to stop casting.
6. Casting video content
We will extend the sample app to also play videos remotely on a Cast device. To do that we need to listen to the various events generated by the Cast framework.
Casting media
At a high level, if you want to play a media on a Cast device, the following needs to happen:
- Create a
GCKMediaInformation
object from the Cast SDK that models a media item. - The user connects to the Cast device to launch your receiver application.
- Load the
GCKMediaInformation
object into your receiver and play the content. - Track the media status.
- Send playback commands to the receiver based on user interactions.
Step 1 amounts to mapping one object to another; GCKMediaInformation
is something that the Cast SDK understands and MediaItem
is our app's encapsulation for a media item; we can easily map a MediaItem
to a GCKMediaInformation
. We have already done the Step 2 in the previous section. Step 3 is easy to do with the Cast SDK.
The sample app MediaViewController
already distinguishes between local vs remote playback by using this enum:
enum PlaybackMode: Int {
case none = 0
case local
case remote
}
private var playbackMode = PlaybackMode.none
It's not important in this codelab for you to understand exactly how all the sample player logic works. It is important to understand that your app's media player will have to be modified to be aware of the two playback locations in a similar way.
At the moment the local player is always in the local playback state since it doesn't know anything about the Casting states yet. We need to update the UI based on state transitions that happen in the Cast framework. For example, if we start casting, we need to stop the local playback and disable some controls. Similarly, if we stop casting when we are in this view controller, we need to transition to local playback. To handle that we need to listen to the various events generated by the Cast framework.
Cast session management
For the Cast framework a Cast session combines the steps of connecting to a device, launching (or joining), connecting to a receiver application, and initializing a media control channel if appropriate. The media control channel is how the Cast framework sends and receives messages from the receiver media player.
The Cast session will be started automatically when user selects a device from the Cast button, and will be stopped automatically when user disconnects. Reconnecting to a receiver session due to networking issues is also automatically handled by the Cast framework.
Cast sessions are managed by the GCKSessionManager
, which can be accessed via GCKCastContext.sharedInstance().sessionManager
. The GCKSessionManagerListener
callbacks can be used to monitor session events, such as creation, suspension, resumption, and termination.
First we need to register our session listener and initialize some variables:
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
private var sessionManager: GCKSessionManager!
...
required init?(coder: NSCoder) {
super.init(coder: coder)
sessionManager = GCKCastContext.sharedInstance().sessionManager
...
}
override func viewWillAppear(_ animated: Bool) {
...
let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
if hasConnectedSession, (playbackMode != .remote) {
populateMediaInfo(false, playPosition: 0)
switchToRemotePlayback()
} else if sessionManager.currentSession == nil, (playbackMode != .local) {
switchToLocalPlayback()
}
sessionManager.add(self)
...
}
override func viewWillDisappear(_ animated: Bool) {
...
sessionManager.remove(self)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
super.viewWillDisappear(animated)
}
func switchToLocalPlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
}
func switchToRemotePlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
...
}
// MARK: - GCKSessionManagerListener
func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
print("MediaViewController: sessionManager didStartSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
print("MediaViewController: sessionManager didResumeSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
print("session ended with error: \(String(describing: error))")
let message = "The Casting session has ended.\n\(String(describing: error))"
if let window = appDelegate?.window {
Toast.displayMessage(message, for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
if let error = error {
showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
}
setQueueButtonVisible(false)
}
func sessionManager(_: GCKSessionManager,
didFailToResumeSession _: GCKSession, withError _: Error?) {
if let window = UIApplication.shared.delegate?.window {
Toast.displayMessage("The Casting session could not be resumed.",
for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
...
}
In MediaViewController
, we are interested to be informed when we get connected or disconnected from the Cast device so we can switch to or from the local player. Note that connectivity can be disrupted not only by the instance of your application running on your mobile device, but it can also be disrupted by another instance of your (or another) application running on a different mobile device.
The currently active session is accessible as GCKCastContext.sharedInstance().sessionManager.currentCastSession
. Sessions are created and torn down automatically in response to user gestures from the Cast dialogs.
Loading media
In the Cast SDK, the GCKRemoteMediaClient
provides a set of convenient APIs for managing the remote media playback on the receiver. For a GCKCastSession
that supports media playback, an instance of GCKRemoteMediaClient
will be created automatically by the SDK. It can be accessed as the remoteMediaClient
property of the GCKCastSession
instance.
Add the following code to MediaViewController.swift
to load the currently selected video on the receiver:
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
@objc func playSelectedItemRemotely() {
loadSelectedItem(byAppending: false)
}
/**
* Loads the currently selected item in the current cast media session.
* @param appending If YES, the item is appended to the current queue if there
* is one. If NO, or if
* there is no queue, a new queue containing only the selected item is created.
*/
func loadSelectedItem(byAppending appending: Bool) {
print("enqueue item \(String(describing: mediaInfo))")
if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = true
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
let mediaQueueItem = mediaQueueItemBuilder.build()
if appending {
let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
request.delegate = self
} else {
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
request.delegate = self
}
}
}
...
}
Now update various existing methods to use the Cast Session logic to support remote playback:
required init?(coder: NSCoder) {
super.init(coder: coder)
...
castMediaController = GCKUIMediaController()
...
}
func switchToLocalPlayback() {
print("switchToLocalPlayback")
if playbackMode == .local {
return
}
setQueueButtonVisible(false)
var playPosition: TimeInterval = 0
var paused: Bool = false
var ended: Bool = false
if playbackMode == .remote {
playPosition = castMediaController.lastKnownStreamPosition
paused = (castMediaController.lastKnownPlayerState == .paused)
ended = (castMediaController.lastKnownPlayerState == .idle)
print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
}
populateMediaInfo((!paused && !ended), playPosition: playPosition)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
playbackMode = .local
}
func switchToRemotePlayback() {
print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
if playbackMode == .remote {
return
}
// If we were playing locally, load the local media on the remote player
if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
print("loading media: \(String(describing: mediaInfo))")
let paused: Bool = (_localPlayerView.playerState == .paused)
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = !paused
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
let mediaQueueItem = mediaQueueItemBuilder.build()
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
request?.delegate = self
}
_localPlayerView.stop()
_localPlayerView.showSplashScreen()
setQueueButtonVisible(true)
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
playbackMode = .remote
}
/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
let hasConnectedCastSession = sessionManager.hasConnectedCastSession
if mediaInfo != nil, hasConnectedCastSession() {
// Display an alert box to allow the user to add to queue or play
// immediately.
if actionSheet == nil {
actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
actionSheet?.addAction(withTitle: "Play Now", target: self,
selector: #selector(playSelectedItemRemotely))
}
actionSheet?.present(in: self, sourceView: _localPlayerView)
return false
}
return true
}
Now, run the app on your mobile device. Connect to your Cast device and start playing a video. You should see the video playing on the receiver.
7. Mini controller
The Cast Design Checklist requires that all Cast apps provide mini controller to appear when the user navigates away from the current content page. The mini controller provide instant access and a visible reminder for the current Cast session.
The Cast SDK provides a control bar, GCKUIMiniMediaControlsViewController
, which can be added to the scenes in which you want to show the persistent controls.
For the sample app, we are going to use the GCKUICastContainerViewController
which wraps another view controller and adds a GCKUIMiniMediaControlsViewController
at the bottom.
Modify the AppDelegate.swift
file and add the following code for if useCastContainerViewController
condition in the following method:
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
as? UINavigationController else { return false }
let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
as GCKUICastContainerViewController
castContainerVC.miniMediaControlsItemEnabled = true
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = castContainerVC
window?.makeKeyAndVisible()
...
}
Add this property and setter/getter to control the visibility of the mini controller (we will use these in a later section):
var isCastControlBarsEnabled: Bool {
get {
if useCastContainerViewController {
let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
return castContainerVC!.miniMediaControlsItemEnabled
} else {
let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
return rootContainerVC!.miniMediaControlsViewEnabled
}
}
set(notificationsEnabled) {
if useCastContainerViewController {
var castContainerVC: GCKUICastContainerViewController?
castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
} else {
var rootContainerVC: RootContainerViewController?
rootContainerVC = (window?.rootViewController as? RootContainerViewController)
rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
}
}
}
Run the app and cast a video. When playback starts on the receiver you should see the mini controller appear at the bottom of each scene. You can control the remote playback using the mini controller. If you navigate between the browse activity and the local player activity, the mini controller state should stay in sync with the receiver media playback status.
8. Introductory overlay
The Google Cast design checklist requires a sender app to introduce the Cast button to existing users to let them know that the sender app now supports Casting and also helps users new to Google Cast.
The GCKCastContext
class has a method, presentCastInstructionsViewControllerOnce
, that can be used to highlight the Cast button when it is first shown to users. Add the following code to MediaViewController.swift
and MediaTableViewController.swift
:
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
name: NSNotification.Name.gckCastStateDidChange,
object: GCKCastContext.sharedInstance())
}
@objc func castDeviceDidChange(_: Notification) {
if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
// You can present the instructions on how to use Google Cast on
// the first time the user uses you app
GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
}
}
Run the app on your mobile device and you should see the introductory overlay.
9. Expanded controller
The Google Cast design checklist requires a sender app to provide expanded controller for the media being cast. The expanded controller is a full screen version of the mini controller.
The expanded controller is a full screen view which offers full control of the remote media playback. This view should allow a casting app to manage every manageable aspect of a cast session, with the exception of receiver volume control and session lifecycle (connect/stop casting). It also provides all the status information about the media session (artwork, title, subtitle, and so forth).
The functionality of this view is implemented by the GCKUIExpandedMediaControlsViewController
class.
The first thing you have to do is enable the default expanded controller in the cast context. Modify AppDelegate.swift
to enable the default expanded controller:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
// Add after the setShareInstanceWith(options) is set.
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
...
}
...
}
Add the following code to MediaViewController.swift
to load the expanded controller when the user starts to cast a video:
@objc func playSelectedItemRemotely() {
...
appDelegate?.isCastControlBarsEnabled = false
GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}
The expanded controller will also be launched automatically when the user taps the mini controller.
Run the app and cast a video. You should see the expanded controller. Navigate back to the list of videos and when you click on the mini controller, the expanded controller will be loaded again.
10. Add Cast Connect support
Cast Connect library allows existing sender applications to communicate with Android TV applications via the Cast protocol. Cast Connect builds on top of the Cast infrastructure, with your Android TV app acting as a receiver.
Dependencies
In your Podfile
, make sure the google-cast-sdk
is pointed to 4.4.8
or higher as listed below. If you made a modification to the file, run pod update
from the console to sync the change with your project.
pod 'google-cast-sdk', '>=4.4.8'
GCKLaunchOptions
In order to launch the Android TV application, also referred to as the Android Receiver, we need to set the androidReceiverCompatible
flag to true in the GCKLaunchOptions
object. This GCKLaunchOptions
object dictates how the receiver is launched and is passed to the GCKCastOptions
which are set in the shared instance using GCKCastContext.setSharedInstanceWith
.
Add the following lines to your AppDelegate.swift
:
let options = GCKCastOptions(discoveryCriteria:
GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
Set Launch Credentials
On the sender side, you can specify GCKCredentialsData
to represent who is joining the session. The credentials
is a string which can be user-defined, as long as your ATV app can understand it. The GCKCredentialsData
is only passed to your Android TV app during launch or join time. If you set it again while you are connected, it won't be passed to your Android TV app.
In order to set Launch Credentials GCKCredentialsData
needs to be defined anytime after the GCKLaunchOptions
are set. To demonstrate this, let's add logic for the Creds button to set credentials to be passed along when the session is established. Add the following code to your MediaTableViewController.swift
:
class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
...
private var credentials: String? = nil
...
override func viewDidLoad() {
...
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
target: self, action: #selector(toggleLaunchCreds))
...
setLaunchCreds()
}
...
@objc func toggleLaunchCreds(_: Any){
if (credentials == nil) {
credentials = "{\"userId\":\"id123\"}"
} else {
credentials = nil
}
Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
print("Credentials set: "+(credentials ?? "Null"))
setLaunchCreds()
}
...
func setLaunchCreds() {
GCKCastContext.sharedInstance()
.setLaunch(GCKCredentialsData(credentials: credentials))
}
}
Set Credentials on Load Request
In order to handle credentials
on both your Web and Android TV Receiver apps, add the following code in your MediaTableViewController.swift
class under loadSelectedItem
function:
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...
Depending on the receiver app your sender is casting to, the SDK would automatically apply the above credentials to the ongoing session.
Testing Cast Connect
Steps to install the Android TV APK on Chromecast with Google TV
- Find the IP Address of your Android TV device. Usually, it's available under Settings > Network & Internet > (Network name your device is connected to) . On the right hand it will show the details and your device's IP on the network.
- Use the IP address for your device to connect to it via ADB using the terminal:
$ adb connect <device_ip_address>:5555
- From your terminal window, navigate into the top level folder for the codelab samples that you downloaded at the start of this codelab. Например:
$ cd Desktop/ios_codelab_src
- Install the .apk file in this folder to your Android TV by running:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- You should now be able to see an app by the name of Cast Videos in the Your Apps menu on your Android TV device.
- Once done, build and run the app on an emulator or a mobile device. On establishing a cast session with your Android TV device, it should now launch the Android Receiver application on your Android TV. Playing a video from your iOS mobile sender, should launch the video in the Android Receiver and allow you to control playback using the remote for your Android TV device.
11. Customize Cast widgets
Initialization
Start with the App-Done folder. Add the following to the applicationDidFinishLaunchingWithOptions
method in your AppDelegate.swift
file.
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let styler = GCKUIStyle.sharedInstance()
...
}
Once you are done applying one or more customizations as mentioned in the rest of this codelab, commit the styles by calling the code below
styler.apply()
Customizing Cast views
You can customize all views that the Cast Application Framework manages by having default styling guidelines across views. As an example, let's change the icon tint color.
styler.castViews.iconTintColor = .lightGray
You can override defaults on a per-screen basis if required. For example, to override the lightGrayColor for the icon tint color just for the expanded media controller.
styler.castViews.mediaControl.expandedController.iconTintColor = .green
Changing colors
You can customize the background color for all views (or individually for each view). The following code sets the background color to blue for all your Cast Application Framework provided views.
styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow
Changing fonts
You can customize fonts for different labels seen within cast views. Let's set all fonts to 'Courier-Oblique' for illustration purposes.
styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)
Changing default button images
Add your own custom images to the project, and assign the images to your buttons to style them.
let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
styler.castViews.muteOnImage = muteOnImage
}
Changing the Cast button theme
You can also theme Cast Widgets using the UIAppearance Protocol. The following code themes the GCKUICastButton on all the views it appears:
GCKUICastButton.appearance().tintColor = UIColor.gray
12. Congratulations
You now know how to Cast-enable a video app using the Cast SDK widgets on iOS.
For more details, see the iOS Sender developer guide.
1. Обзор
This codelab will teach you how to modify an existing iOS video app to cast content on a Google Cast-enabled device.
What is Google Cast?
Google Cast allows users to cast content from a mobile device to a TV. Users can then use their mobile device as a remote control for media playback on the TV.
The Google Cast SDK lets you extend your app to control Google Cast enabled devices (such as a TV or sound system). The Cast SDK allows you to add the necessary UI components based on the Google Cast Design Checklist .
The Google Cast Design Checklist is provided to make the Cast user experience simple and predictable across all supported platforms.
What are we going to be building?
When you have completed this codelab, you will have an iOS video app that will be able to Cast videos to a Google Cast device.
Что вы узнаете
- How to add the Google Cast SDK to a sample video app.
- How to add the Cast button for selecting a Google Cast device.
- How to connect to a Cast device and launch a media receiver.
- How to cast a video.
- How to add a Cast mini controller to your app.
- How to add an expanded controller.
- How to provide an introductory overlay.
- How to customize Cast widgets.
- How to integrate Cast Connect
Что вам понадобится
- The latest Xcode .
- One mobile device with iOS 9 or later (or the Xcode Simulator).
- A USB data cable to connect your mobile device to your development computer (if using a device).
- A Google Cast device such as a Chromecast or Android TV configured with internet access.
- A TV or monitor with HDMI input.
- A Chromecast with Google TV is required to test Cast Connect integration but is optional for the rest of the Codelab. If you do not have one, feel free to skip the Add Cast Connect Support step, towards the end of this tutorial.
Experience
- You will need to have previous iOS development knowledge.
- You will also need previous knowledge of watching TV :)
How will you use this tutorial?
How would you rate your experience with building iOS apps?
How would you rate your experience with watching TV?
2. Получите пример кода
You can either download all the sample code to your computer...
and unpack the downloaded zip file.
3. Run the sample app
First, let's see what the completed sample app looks like. The app is a basic video player. The user can select a video from a list and can then play the video locally on the device or Cast it to a Google Cast device.
With the code downloaded, the following instructions describe how to open and run the completed sample app in Xcode:
Frequently asked questions
CocoaPods setup
To setup CocoaPods, go to your console and install using the default Ruby available on macOS:
sudo gem install cocoapods
If you have any issues, refer to the official documentation to download and install the dependency manager.
Project setup
- Go to your terminal and navigate to the codelab directory.
- Install the dependencies from the Podfile.
cd app-done pod update pod install
- Open Xcode and select Open another project...
- Select the
CastVideos-ios.xcworkspace
file from theapp-done
directory in the sample code folder.
Run the app
Select the target and simulator, and then run the app:
You should see the video app appear after a few seconds.
Be sure to click 'Allow' when the notification appears about accepting incoming network connects. The Cast icon will not appear if this option is not accepted.
Click the Cast button and select your Google Cast device.
Select a video, click on the play button.
The video will start playing on your Google Cast device.
The expanded controller will be displayed. You can use the play/pause button to control the playback.
Navigate back to the list of videos.
A mini controller is now visible at the bottom of the screen.
Click on the pause button in the mini controller to pause the video on the receiver. Click on the play button in the mini controller to continue playing the video again.
Click on the Cast button to stop casting to the Google Cast device.
4. Prepare the start project
We need to add support for Google Cast to the start app you downloaded. Here are some Google Cast terminology that we will be using in this codelab:
- a sender app runs on a mobile device or laptop,
- a receiver app runs on the Google Cast device.
Project setup
Now you're ready to build on top of the starter project using Xcode:
- Go to your terminal and navigate to the codelab directory.
- Install the dependencies from the Podfile.
cd app-start pod update pod install
- Open Xcode and select Open another project...
- Select the
CastVideos-ios.xcworkspace
file from theapp-start
directory in the sample code folder.
App design
The app fetches a list of videos from a remote web server and provides a list for the user to browse. Users can select a video to see the details or play the video locally on the mobile device.
The app consists of two main view controllers: MediaTableViewController
and MediaViewController.
MediaTableViewController
This UITableViewController displays a list of videos from a MediaListModel
instance. The list of videos and their associated metadata are hosted on a remote server as a JSON file. MediaListModel
fetches this JSON and processes it to build a list of MediaItem
objects.
A MediaItem
object models a video and its associated metadata, such as its title, description, URL for an image, and URL for the stream.
MediaTableViewController
creates a MediaListModel
instance and then registers itself as a MediaListModelDelegate
to be informed when the media metadata has been downloaded so it can load the table view.
The user is presented with a list of video thumbnails with a short description for each video. When an item is selected, the corresponding MediaItem
is passed to the MediaViewController
.
MediaViewController
This view controller displays the metadata about a particular video and allows the user to play the video locally on the mobile device.
The view controller hosts a LocalPlayerView
, some media controls, and a text area to show the description of the selected video. The player covers the top portion of the screen, leaving room for the detailed description of the video beneath The user can play/pause or seek the local video playback.
Frequently asked questions
5. Adding the Cast button
A Cast-enabled application displays the Cast button in each of its view controllers. Clicking on the Cast button displays a list of Cast devices which a user can select. If the user was playing content locally on the sender device, selecting a Cast device starts or resumes playback on that Cast device. At any time during a Cast session, the user can click on the Cast button and stop casting your application to the Cast device. The user must be able to connect to or disconnect from the Cast device while in any screen of your application, as described in the Google Cast Design Checklist .
Configuration
The start project requires the same dependencies and Xcode setup as you did for the completed sample app. Return to that section and follow the same steps to add the GoogleCast.framework
to the start app project.
Initialization
The Cast framework has a global singleton object, the GCKCastContext
, which coordinates all of the framework's activities. This object must be initialized early in the application's lifecycle, typically in the application(_:didFinishLaunchingWithOptions:)
method of the app delegate, so that automatic session resumption on the sender application restart can trigger properly and scanning for devices can start.
A GCKCastOptions
object must be supplied when initializing the GCKCastContext
. This class contains options that affect the behavior of the framework. The most important of these is the receiver application ID, which is used to filter Cast device discovery results and to launch the receiver application when a Cast session is started.
The application(_:didFinishLaunchingWithOptions:)
method is also a good place to set up a logging delegate to receive the logging messages from Cast framework. These can be useful for debugging and troubleshooting.
When you develop your own Cast-enabled app, you have to register as a Cast developer and then obtain an application ID for your app. For this codelab, we will be using a sample app ID.
Add the following code to AppDelegate.swift
to initialize GCKCastContext
with the application ID from the user defaults, and add a logger for the Google Cast framework:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
fileprivate var enableSDKLogging = true
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
window?.clipsToBounds = true
setupCastLogging()
...
}
...
func setupCastLogging() {
let logFilter = GCKLoggerFilter()
let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
"GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
GCKLogger.sharedInstance().filter = logFilter
GCKLogger.sharedInstance().delegate = self
}
}
...
// MARK: - GCKLoggerDelegate
extension AppDelegate: GCKLoggerDelegate {
func logMessage(_ message: String,
at _: GCKLoggerLevel,
fromFunction function: String,
location: String) {
if enableSDKLogging {
// Send SDK's log messages directly to the console.
print("\(location): \(function) - \(message)")
}
}
}
Cast button
Now that the GCKCastContext
is initialized, we need to add the Cast button to allow the user to select a Cast device. The Cast SDK provides a Cast button component called GCKUICastButton
as a UIButton
subclass. It can be added to the application's title bar by wrapping it in a UIBarButtonItem
. We need to add the Cast button to both the MediaTableViewController
and the MediaViewController
.
Add the following code to MediaTableViewController.swift
and MediaViewController.swift
:
import GoogleCast
@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
MediaListModelDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
print("MediaTableViewController - viewDidLoad")
super.viewDidLoad()
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Next, add the following code to your MediaViewController.swift
:
import GoogleCast
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
LocalPlayerViewDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
super.viewDidLoad()
print("in MediaViewController viewDidLoad")
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Now run the app. You should see a Cast button in the app's navigation bar and when you click on it, it will list the Cast devices on your local network. Device discovery is managed automatically by the GCKCastContext
. Select your Cast device and the sample receiver app will load on the Cast device. You can navigate between the browse activity and the local player activity and the Cast button state is kept in sync.
We haven't hooked up any support for media playback, so you can't play videos on the Cast device yet. Click on the Cast button to stop casting.
6. Casting video content
We will extend the sample app to also play videos remotely on a Cast device. To do that we need to listen to the various events generated by the Cast framework.
Casting media
At a high level, if you want to play a media on a Cast device, the following needs to happen:
- Create a
GCKMediaInformation
object from the Cast SDK that models a media item. - The user connects to the Cast device to launch your receiver application.
- Load the
GCKMediaInformation
object into your receiver and play the content. - Track the media status.
- Send playback commands to the receiver based on user interactions.
Step 1 amounts to mapping one object to another; GCKMediaInformation
is something that the Cast SDK understands and MediaItem
is our app's encapsulation for a media item; we can easily map a MediaItem
to a GCKMediaInformation
. We have already done the Step 2 in the previous section. Step 3 is easy to do with the Cast SDK.
The sample app MediaViewController
already distinguishes between local vs remote playback by using this enum:
enum PlaybackMode: Int {
case none = 0
case local
case remote
}
private var playbackMode = PlaybackMode.none
It's not important in this codelab for you to understand exactly how all the sample player logic works. It is important to understand that your app's media player will have to be modified to be aware of the two playback locations in a similar way.
At the moment the local player is always in the local playback state since it doesn't know anything about the Casting states yet. We need to update the UI based on state transitions that happen in the Cast framework. For example, if we start casting, we need to stop the local playback and disable some controls. Similarly, if we stop casting when we are in this view controller, we need to transition to local playback. To handle that we need to listen to the various events generated by the Cast framework.
Cast session management
For the Cast framework a Cast session combines the steps of connecting to a device, launching (or joining), connecting to a receiver application, and initializing a media control channel if appropriate. The media control channel is how the Cast framework sends and receives messages from the receiver media player.
The Cast session will be started automatically when user selects a device from the Cast button, and will be stopped automatically when user disconnects. Reconnecting to a receiver session due to networking issues is also automatically handled by the Cast framework.
Cast sessions are managed by the GCKSessionManager
, which can be accessed via GCKCastContext.sharedInstance().sessionManager
. The GCKSessionManagerListener
callbacks can be used to monitor session events, such as creation, suspension, resumption, and termination.
First we need to register our session listener and initialize some variables:
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
private var sessionManager: GCKSessionManager!
...
required init?(coder: NSCoder) {
super.init(coder: coder)
sessionManager = GCKCastContext.sharedInstance().sessionManager
...
}
override func viewWillAppear(_ animated: Bool) {
...
let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
if hasConnectedSession, (playbackMode != .remote) {
populateMediaInfo(false, playPosition: 0)
switchToRemotePlayback()
} else if sessionManager.currentSession == nil, (playbackMode != .local) {
switchToLocalPlayback()
}
sessionManager.add(self)
...
}
override func viewWillDisappear(_ animated: Bool) {
...
sessionManager.remove(self)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
super.viewWillDisappear(animated)
}
func switchToLocalPlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
}
func switchToRemotePlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
...
}
// MARK: - GCKSessionManagerListener
func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
print("MediaViewController: sessionManager didStartSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
print("MediaViewController: sessionManager didResumeSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
print("session ended with error: \(String(describing: error))")
let message = "The Casting session has ended.\n\(String(describing: error))"
if let window = appDelegate?.window {
Toast.displayMessage(message, for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
if let error = error {
showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
}
setQueueButtonVisible(false)
}
func sessionManager(_: GCKSessionManager,
didFailToResumeSession _: GCKSession, withError _: Error?) {
if let window = UIApplication.shared.delegate?.window {
Toast.displayMessage("The Casting session could not be resumed.",
for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
...
}
In MediaViewController
, we are interested to be informed when we get connected or disconnected from the Cast device so we can switch to or from the local player. Note that connectivity can be disrupted not only by the instance of your application running on your mobile device, but it can also be disrupted by another instance of your (or another) application running on a different mobile device.
The currently active session is accessible as GCKCastContext.sharedInstance().sessionManager.currentCastSession
. Sessions are created and torn down automatically in response to user gestures from the Cast dialogs.
Loading media
In the Cast SDK, the GCKRemoteMediaClient
provides a set of convenient APIs for managing the remote media playback on the receiver. For a GCKCastSession
that supports media playback, an instance of GCKRemoteMediaClient
will be created automatically by the SDK. It can be accessed as the remoteMediaClient
property of the GCKCastSession
instance.
Add the following code to MediaViewController.swift
to load the currently selected video on the receiver:
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
@objc func playSelectedItemRemotely() {
loadSelectedItem(byAppending: false)
}
/**
* Loads the currently selected item in the current cast media session.
* @param appending If YES, the item is appended to the current queue if there
* is one. If NO, or if
* there is no queue, a new queue containing only the selected item is created.
*/
func loadSelectedItem(byAppending appending: Bool) {
print("enqueue item \(String(describing: mediaInfo))")
if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = true
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
let mediaQueueItem = mediaQueueItemBuilder.build()
if appending {
let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
request.delegate = self
} else {
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
request.delegate = self
}
}
}
...
}
Now update various existing methods to use the Cast Session logic to support remote playback:
required init?(coder: NSCoder) {
super.init(coder: coder)
...
castMediaController = GCKUIMediaController()
...
}
func switchToLocalPlayback() {
print("switchToLocalPlayback")
if playbackMode == .local {
return
}
setQueueButtonVisible(false)
var playPosition: TimeInterval = 0
var paused: Bool = false
var ended: Bool = false
if playbackMode == .remote {
playPosition = castMediaController.lastKnownStreamPosition
paused = (castMediaController.lastKnownPlayerState == .paused)
ended = (castMediaController.lastKnownPlayerState == .idle)
print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
}
populateMediaInfo((!paused && !ended), playPosition: playPosition)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
playbackMode = .local
}
func switchToRemotePlayback() {
print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
if playbackMode == .remote {
return
}
// If we were playing locally, load the local media on the remote player
if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
print("loading media: \(String(describing: mediaInfo))")
let paused: Bool = (_localPlayerView.playerState == .paused)
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = !paused
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
let mediaQueueItem = mediaQueueItemBuilder.build()
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
request?.delegate = self
}
_localPlayerView.stop()
_localPlayerView.showSplashScreen()
setQueueButtonVisible(true)
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
playbackMode = .remote
}
/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
let hasConnectedCastSession = sessionManager.hasConnectedCastSession
if mediaInfo != nil, hasConnectedCastSession() {
// Display an alert box to allow the user to add to queue or play
// immediately.
if actionSheet == nil {
actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
actionSheet?.addAction(withTitle: "Play Now", target: self,
selector: #selector(playSelectedItemRemotely))
}
actionSheet?.present(in: self, sourceView: _localPlayerView)
return false
}
return true
}
Now, run the app on your mobile device. Connect to your Cast device and start playing a video. You should see the video playing on the receiver.
7. Mini controller
The Cast Design Checklist requires that all Cast apps provide mini controller to appear when the user navigates away from the current content page. The mini controller provide instant access and a visible reminder for the current Cast session.
The Cast SDK provides a control bar, GCKUIMiniMediaControlsViewController
, which can be added to the scenes in which you want to show the persistent controls.
For the sample app, we are going to use the GCKUICastContainerViewController
which wraps another view controller and adds a GCKUIMiniMediaControlsViewController
at the bottom.
Modify the AppDelegate.swift
file and add the following code for if useCastContainerViewController
condition in the following method:
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
as? UINavigationController else { return false }
let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
as GCKUICastContainerViewController
castContainerVC.miniMediaControlsItemEnabled = true
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = castContainerVC
window?.makeKeyAndVisible()
...
}
Add this property and setter/getter to control the visibility of the mini controller (we will use these in a later section):
var isCastControlBarsEnabled: Bool {
get {
if useCastContainerViewController {
let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
return castContainerVC!.miniMediaControlsItemEnabled
} else {
let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
return rootContainerVC!.miniMediaControlsViewEnabled
}
}
set(notificationsEnabled) {
if useCastContainerViewController {
var castContainerVC: GCKUICastContainerViewController?
castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
} else {
var rootContainerVC: RootContainerViewController?
rootContainerVC = (window?.rootViewController as? RootContainerViewController)
rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
}
}
}
Run the app and cast a video. When playback starts on the receiver you should see the mini controller appear at the bottom of each scene. You can control the remote playback using the mini controller. If you navigate between the browse activity and the local player activity, the mini controller state should stay in sync with the receiver media playback status.
8. Introductory overlay
The Google Cast design checklist requires a sender app to introduce the Cast button to existing users to let them know that the sender app now supports Casting and also helps users new to Google Cast.
The GCKCastContext
class has a method, presentCastInstructionsViewControllerOnce
, that can be used to highlight the Cast button when it is first shown to users. Add the following code to MediaViewController.swift
and MediaTableViewController.swift
:
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
name: NSNotification.Name.gckCastStateDidChange,
object: GCKCastContext.sharedInstance())
}
@objc func castDeviceDidChange(_: Notification) {
if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
// You can present the instructions on how to use Google Cast on
// the first time the user uses you app
GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
}
}
Run the app on your mobile device and you should see the introductory overlay.
9. Expanded controller
The Google Cast design checklist requires a sender app to provide expanded controller for the media being cast. The expanded controller is a full screen version of the mini controller.
The expanded controller is a full screen view which offers full control of the remote media playback. This view should allow a casting app to manage every manageable aspect of a cast session, with the exception of receiver volume control and session lifecycle (connect/stop casting). It also provides all the status information about the media session (artwork, title, subtitle, and so forth).
The functionality of this view is implemented by the GCKUIExpandedMediaControlsViewController
class.
The first thing you have to do is enable the default expanded controller in the cast context. Modify AppDelegate.swift
to enable the default expanded controller:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
// Add after the setShareInstanceWith(options) is set.
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
...
}
...
}
Add the following code to MediaViewController.swift
to load the expanded controller when the user starts to cast a video:
@objc func playSelectedItemRemotely() {
...
appDelegate?.isCastControlBarsEnabled = false
GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}
The expanded controller will also be launched automatically when the user taps the mini controller.
Run the app and cast a video. You should see the expanded controller. Navigate back to the list of videos and when you click on the mini controller, the expanded controller will be loaded again.
10. Add Cast Connect support
Cast Connect library allows existing sender applications to communicate with Android TV applications via the Cast protocol. Cast Connect builds on top of the Cast infrastructure, with your Android TV app acting as a receiver.
Dependencies
In your Podfile
, make sure the google-cast-sdk
is pointed to 4.4.8
or higher as listed below. If you made a modification to the file, run pod update
from the console to sync the change with your project.
pod 'google-cast-sdk', '>=4.4.8'
GCKLaunchOptions
In order to launch the Android TV application, also referred to as the Android Receiver, we need to set the androidReceiverCompatible
flag to true in the GCKLaunchOptions
object. This GCKLaunchOptions
object dictates how the receiver is launched and is passed to the GCKCastOptions
which are set in the shared instance using GCKCastContext.setSharedInstanceWith
.
Add the following lines to your AppDelegate.swift
:
let options = GCKCastOptions(discoveryCriteria:
GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
Set Launch Credentials
On the sender side, you can specify GCKCredentialsData
to represent who is joining the session. The credentials
is a string which can be user-defined, as long as your ATV app can understand it. The GCKCredentialsData
is only passed to your Android TV app during launch or join time. If you set it again while you are connected, it won't be passed to your Android TV app.
In order to set Launch Credentials GCKCredentialsData
needs to be defined anytime after the GCKLaunchOptions
are set. To demonstrate this, let's add logic for the Creds button to set credentials to be passed along when the session is established. Add the following code to your MediaTableViewController.swift
:
class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
...
private var credentials: String? = nil
...
override func viewDidLoad() {
...
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
target: self, action: #selector(toggleLaunchCreds))
...
setLaunchCreds()
}
...
@objc func toggleLaunchCreds(_: Any){
if (credentials == nil) {
credentials = "{\"userId\":\"id123\"}"
} else {
credentials = nil
}
Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
print("Credentials set: "+(credentials ?? "Null"))
setLaunchCreds()
}
...
func setLaunchCreds() {
GCKCastContext.sharedInstance()
.setLaunch(GCKCredentialsData(credentials: credentials))
}
}
Set Credentials on Load Request
In order to handle credentials
on both your Web and Android TV Receiver apps, add the following code in your MediaTableViewController.swift
class under loadSelectedItem
function:
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...
Depending on the receiver app your sender is casting to, the SDK would automatically apply the above credentials to the ongoing session.
Testing Cast Connect
Steps to install the Android TV APK on Chromecast with Google TV
- Find the IP Address of your Android TV device. Usually, it's available under Settings > Network & Internet > (Network name your device is connected to) . On the right hand it will show the details and your device's IP on the network.
- Use the IP address for your device to connect to it via ADB using the terminal:
$ adb connect <device_ip_address>:5555
- From your terminal window, navigate into the top level folder for the codelab samples that you downloaded at the start of this codelab. Например:
$ cd Desktop/ios_codelab_src
- Install the .apk file in this folder to your Android TV by running:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- You should now be able to see an app by the name of Cast Videos in the Your Apps menu on your Android TV device.
- Once done, build and run the app on an emulator or a mobile device. On establishing a cast session with your Android TV device, it should now launch the Android Receiver application on your Android TV. Playing a video from your iOS mobile sender, should launch the video in the Android Receiver and allow you to control playback using the remote for your Android TV device.
11. Customize Cast widgets
Initialization
Start with the App-Done folder. Add the following to the applicationDidFinishLaunchingWithOptions
method in your AppDelegate.swift
file.
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let styler = GCKUIStyle.sharedInstance()
...
}
Once you are done applying one or more customizations as mentioned in the rest of this codelab, commit the styles by calling the code below
styler.apply()
Customizing Cast views
You can customize all views that the Cast Application Framework manages by having default styling guidelines across views. As an example, let's change the icon tint color.
styler.castViews.iconTintColor = .lightGray
You can override defaults on a per-screen basis if required. For example, to override the lightGrayColor for the icon tint color just for the expanded media controller.
styler.castViews.mediaControl.expandedController.iconTintColor = .green
Changing colors
You can customize the background color for all views (or individually for each view). The following code sets the background color to blue for all your Cast Application Framework provided views.
styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow
Changing fonts
You can customize fonts for different labels seen within cast views. Let's set all fonts to 'Courier-Oblique' for illustration purposes.
styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)
Changing default button images
Add your own custom images to the project, and assign the images to your buttons to style them.
let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
styler.castViews.muteOnImage = muteOnImage
}
Changing the Cast button theme
You can also theme Cast Widgets using the UIAppearance Protocol. The following code themes the GCKUICastButton on all the views it appears:
GCKUICastButton.appearance().tintColor = UIColor.gray
12. Congratulations
You now know how to Cast-enable a video app using the Cast SDK widgets on iOS.
For more details, see the iOS Sender developer guide.
1. Обзор
This codelab will teach you how to modify an existing iOS video app to cast content on a Google Cast-enabled device.
What is Google Cast?
Google Cast allows users to cast content from a mobile device to a TV. Users can then use their mobile device as a remote control for media playback on the TV.
The Google Cast SDK lets you extend your app to control Google Cast enabled devices (such as a TV or sound system). The Cast SDK allows you to add the necessary UI components based on the Google Cast Design Checklist .
The Google Cast Design Checklist is provided to make the Cast user experience simple and predictable across all supported platforms.
What are we going to be building?
When you have completed this codelab, you will have an iOS video app that will be able to Cast videos to a Google Cast device.
Что вы узнаете
- How to add the Google Cast SDK to a sample video app.
- How to add the Cast button for selecting a Google Cast device.
- How to connect to a Cast device and launch a media receiver.
- How to cast a video.
- How to add a Cast mini controller to your app.
- How to add an expanded controller.
- How to provide an introductory overlay.
- How to customize Cast widgets.
- How to integrate Cast Connect
Что вам понадобится
- The latest Xcode .
- One mobile device with iOS 9 or later (or the Xcode Simulator).
- A USB data cable to connect your mobile device to your development computer (if using a device).
- A Google Cast device such as a Chromecast or Android TV configured with internet access.
- A TV or monitor with HDMI input.
- A Chromecast with Google TV is required to test Cast Connect integration but is optional for the rest of the Codelab. If you do not have one, feel free to skip the Add Cast Connect Support step, towards the end of this tutorial.
Experience
- You will need to have previous iOS development knowledge.
- You will also need previous knowledge of watching TV :)
How will you use this tutorial?
How would you rate your experience with building iOS apps?
How would you rate your experience with watching TV?
2. Получите пример кода
You can either download all the sample code to your computer...
and unpack the downloaded zip file.
3. Run the sample app
First, let's see what the completed sample app looks like. The app is a basic video player. The user can select a video from a list and can then play the video locally on the device or Cast it to a Google Cast device.
With the code downloaded, the following instructions describe how to open and run the completed sample app in Xcode:
Frequently asked questions
CocoaPods setup
To setup CocoaPods, go to your console and install using the default Ruby available on macOS:
sudo gem install cocoapods
If you have any issues, refer to the official documentation to download and install the dependency manager.
Project setup
- Go to your terminal and navigate to the codelab directory.
- Install the dependencies from the Podfile.
cd app-done pod update pod install
- Open Xcode and select Open another project...
- Select the
CastVideos-ios.xcworkspace
file from theapp-done
directory in the sample code folder.
Run the app
Select the target and simulator, and then run the app:
You should see the video app appear after a few seconds.
Be sure to click 'Allow' when the notification appears about accepting incoming network connects. The Cast icon will not appear if this option is not accepted.
Click the Cast button and select your Google Cast device.
Select a video, click on the play button.
The video will start playing on your Google Cast device.
The expanded controller will be displayed. You can use the play/pause button to control the playback.
Navigate back to the list of videos.
A mini controller is now visible at the bottom of the screen.
Click on the pause button in the mini controller to pause the video on the receiver. Click on the play button in the mini controller to continue playing the video again.
Click on the Cast button to stop casting to the Google Cast device.
4. Prepare the start project
We need to add support for Google Cast to the start app you downloaded. Here are some Google Cast terminology that we will be using in this codelab:
- a sender app runs on a mobile device or laptop,
- a receiver app runs on the Google Cast device.
Project setup
Now you're ready to build on top of the starter project using Xcode:
- Go to your terminal and navigate to the codelab directory.
- Install the dependencies from the Podfile.
cd app-start pod update pod install
- Open Xcode and select Open another project...
- Select the
CastVideos-ios.xcworkspace
file from theapp-start
directory in the sample code folder.
App design
The app fetches a list of videos from a remote web server and provides a list for the user to browse. Users can select a video to see the details or play the video locally on the mobile device.
The app consists of two main view controllers: MediaTableViewController
and MediaViewController.
MediaTableViewController
This UITableViewController displays a list of videos from a MediaListModel
instance. The list of videos and their associated metadata are hosted on a remote server as a JSON file. MediaListModel
fetches this JSON and processes it to build a list of MediaItem
objects.
A MediaItem
object models a video and its associated metadata, such as its title, description, URL for an image, and URL for the stream.
MediaTableViewController
creates a MediaListModel
instance and then registers itself as a MediaListModelDelegate
to be informed when the media metadata has been downloaded so it can load the table view.
The user is presented with a list of video thumbnails with a short description for each video. When an item is selected, the corresponding MediaItem
is passed to the MediaViewController
.
MediaViewController
This view controller displays the metadata about a particular video and allows the user to play the video locally on the mobile device.
The view controller hosts a LocalPlayerView
, some media controls, and a text area to show the description of the selected video. The player covers the top portion of the screen, leaving room for the detailed description of the video beneath The user can play/pause or seek the local video playback.
Frequently asked questions
5. Adding the Cast button
A Cast-enabled application displays the Cast button in each of its view controllers. Clicking on the Cast button displays a list of Cast devices which a user can select. If the user was playing content locally on the sender device, selecting a Cast device starts or resumes playback on that Cast device. At any time during a Cast session, the user can click on the Cast button and stop casting your application to the Cast device. The user must be able to connect to or disconnect from the Cast device while in any screen of your application, as described in the Google Cast Design Checklist .
Configuration
The start project requires the same dependencies and Xcode setup as you did for the completed sample app. Return to that section and follow the same steps to add the GoogleCast.framework
to the start app project.
Initialization
The Cast framework has a global singleton object, the GCKCastContext
, which coordinates all of the framework's activities. This object must be initialized early in the application's lifecycle, typically in the application(_:didFinishLaunchingWithOptions:)
method of the app delegate, so that automatic session resumption on the sender application restart can trigger properly and scanning for devices can start.
A GCKCastOptions
object must be supplied when initializing the GCKCastContext
. This class contains options that affect the behavior of the framework. The most important of these is the receiver application ID, which is used to filter Cast device discovery results and to launch the receiver application when a Cast session is started.
The application(_:didFinishLaunchingWithOptions:)
method is also a good place to set up a logging delegate to receive the logging messages from Cast framework. These can be useful for debugging and troubleshooting.
When you develop your own Cast-enabled app, you have to register as a Cast developer and then obtain an application ID for your app. For this codelab, we will be using a sample app ID.
Add the following code to AppDelegate.swift
to initialize GCKCastContext
with the application ID from the user defaults, and add a logger for the Google Cast framework:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
fileprivate var enableSDKLogging = true
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID))
options.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(options)
window?.clipsToBounds = true
setupCastLogging()
...
}
...
func setupCastLogging() {
let logFilter = GCKLoggerFilter()
let classesToLog = ["GCKDeviceScanner", "GCKDeviceProvider", "GCKDiscoveryManager", "GCKCastChannel",
"GCKMediaControlChannel", "GCKUICastButton", "GCKUIMediaController", "NSMutableDictionary"]
logFilter.setLoggingLevel(.verbose, forClasses: classesToLog)
GCKLogger.sharedInstance().filter = logFilter
GCKLogger.sharedInstance().delegate = self
}
}
...
// MARK: - GCKLoggerDelegate
extension AppDelegate: GCKLoggerDelegate {
func logMessage(_ message: String,
at _: GCKLoggerLevel,
fromFunction function: String,
location: String) {
if enableSDKLogging {
// Send SDK's log messages directly to the console.
print("\(location): \(function) - \(message)")
}
}
}
Cast button
Now that the GCKCastContext
is initialized, we need to add the Cast button to allow the user to select a Cast device. The Cast SDK provides a Cast button component called GCKUICastButton
as a UIButton
subclass. It can be added to the application's title bar by wrapping it in a UIBarButtonItem
. We need to add the Cast button to both the MediaTableViewController
and the MediaViewController
.
Add the following code to MediaTableViewController.swift
and MediaViewController.swift
:
import GoogleCast
@objc(MediaTableViewController)
class MediaTableViewController: UITableViewController, GCKSessionManagerListener,
MediaListModelDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
print("MediaTableViewController - viewDidLoad")
super.viewDidLoad()
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Next, add the following code to your MediaViewController.swift
:
import GoogleCast
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener, GCKRemoteMediaClientListener,
LocalPlayerViewDelegate, GCKRequestDelegate {
private var castButton: GCKUICastButton!
...
override func viewDidLoad() {
super.viewDidLoad()
print("in MediaViewController viewDidLoad")
...
castButton = GCKUICastButton(frame: CGRect(x: CGFloat(0), y: CGFloat(0),
width: CGFloat(24), height: CGFloat(24)))
// Overwrite the UIAppearance theme in the AppDelegate.
castButton.tintColor = UIColor.white
navigationItem.rightBarButtonItem = UIBarButtonItem(customView: castButton)
...
}
...
}
Now run the app. You should see a Cast button in the app's navigation bar and when you click on it, it will list the Cast devices on your local network. Device discovery is managed automatically by the GCKCastContext
. Select your Cast device and the sample receiver app will load on the Cast device. You can navigate between the browse activity and the local player activity and the Cast button state is kept in sync.
We haven't hooked up any support for media playback, so you can't play videos on the Cast device yet. Click on the Cast button to stop casting.
6. Casting video content
We will extend the sample app to also play videos remotely on a Cast device. To do that we need to listen to the various events generated by the Cast framework.
Casting media
At a high level, if you want to play a media on a Cast device, the following needs to happen:
- Create a
GCKMediaInformation
object from the Cast SDK that models a media item. - The user connects to the Cast device to launch your receiver application.
- Load the
GCKMediaInformation
object into your receiver and play the content. - Track the media status.
- Send playback commands to the receiver based on user interactions.
Step 1 amounts to mapping one object to another; GCKMediaInformation
is something that the Cast SDK understands and MediaItem
is our app's encapsulation for a media item; we can easily map a MediaItem
to a GCKMediaInformation
. We have already done the Step 2 in the previous section. Step 3 is easy to do with the Cast SDK.
The sample app MediaViewController
already distinguishes between local vs remote playback by using this enum:
enum PlaybackMode: Int {
case none = 0
case local
case remote
}
private var playbackMode = PlaybackMode.none
It's not important in this codelab for you to understand exactly how all the sample player logic works. It is important to understand that your app's media player will have to be modified to be aware of the two playback locations in a similar way.
At the moment the local player is always in the local playback state since it doesn't know anything about the Casting states yet. We need to update the UI based on state transitions that happen in the Cast framework. For example, if we start casting, we need to stop the local playback and disable some controls. Similarly, if we stop casting when we are in this view controller, we need to transition to local playback. To handle that we need to listen to the various events generated by the Cast framework.
Cast session management
For the Cast framework a Cast session combines the steps of connecting to a device, launching (or joining), connecting to a receiver application, and initializing a media control channel if appropriate. The media control channel is how the Cast framework sends and receives messages from the receiver media player.
The Cast session will be started automatically when user selects a device from the Cast button, and will be stopped automatically when user disconnects. Reconnecting to a receiver session due to networking issues is also automatically handled by the Cast framework.
Cast sessions are managed by the GCKSessionManager
, which can be accessed via GCKCastContext.sharedInstance().sessionManager
. The GCKSessionManagerListener
callbacks can be used to monitor session events, such as creation, suspension, resumption, and termination.
First we need to register our session listener and initialize some variables:
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
private var sessionManager: GCKSessionManager!
...
required init?(coder: NSCoder) {
super.init(coder: coder)
sessionManager = GCKCastContext.sharedInstance().sessionManager
...
}
override func viewWillAppear(_ animated: Bool) {
...
let hasConnectedSession: Bool = (sessionManager.hasConnectedSession())
if hasConnectedSession, (playbackMode != .remote) {
populateMediaInfo(false, playPosition: 0)
switchToRemotePlayback()
} else if sessionManager.currentSession == nil, (playbackMode != .local) {
switchToLocalPlayback()
}
sessionManager.add(self)
...
}
override func viewWillDisappear(_ animated: Bool) {
...
sessionManager.remove(self)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
super.viewWillDisappear(animated)
}
func switchToLocalPlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
...
}
func switchToRemotePlayback() {
...
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
...
}
// MARK: - GCKSessionManagerListener
func sessionManager(_: GCKSessionManager, didStart session: GCKSession) {
print("MediaViewController: sessionManager didStartSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didResumeSession session: GCKSession) {
print("MediaViewController: sessionManager didResumeSession \(session)")
setQueueButtonVisible(true)
switchToRemotePlayback()
}
func sessionManager(_: GCKSessionManager, didEnd _: GCKSession, withError error: Error?) {
print("session ended with error: \(String(describing: error))")
let message = "The Casting session has ended.\n\(String(describing: error))"
if let window = appDelegate?.window {
Toast.displayMessage(message, for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
func sessionManager(_: GCKSessionManager, didFailToStartSessionWithError error: Error?) {
if let error = error {
showAlert(withTitle: "Failed to start a session", message: error.localizedDescription)
}
setQueueButtonVisible(false)
}
func sessionManager(_: GCKSessionManager,
didFailToResumeSession _: GCKSession, withError _: Error?) {
if let window = UIApplication.shared.delegate?.window {
Toast.displayMessage("The Casting session could not be resumed.",
for: 3, in: window)
}
setQueueButtonVisible(false)
switchToLocalPlayback()
}
...
}
In MediaViewController
, we are interested to be informed when we get connected or disconnected from the Cast device so we can switch to or from the local player. Note that connectivity can be disrupted not only by the instance of your application running on your mobile device, but it can also be disrupted by another instance of your (or another) application running on a different mobile device.
The currently active session is accessible as GCKCastContext.sharedInstance().sessionManager.currentCastSession
. Sessions are created and torn down automatically in response to user gestures from the Cast dialogs.
Loading media
In the Cast SDK, the GCKRemoteMediaClient
provides a set of convenient APIs for managing the remote media playback on the receiver. For a GCKCastSession
that supports media playback, an instance of GCKRemoteMediaClient
will be created automatically by the SDK. It can be accessed as the remoteMediaClient
property of the GCKCastSession
instance.
Add the following code to MediaViewController.swift
to load the currently selected video on the receiver:
@objc(MediaViewController)
class MediaViewController: UIViewController, GCKSessionManagerListener,
GCKRemoteMediaClientListener, LocalPlayerViewDelegate, GCKRequestDelegate {
...
@objc func playSelectedItemRemotely() {
loadSelectedItem(byAppending: false)
}
/**
* Loads the currently selected item in the current cast media session.
* @param appending If YES, the item is appended to the current queue if there
* is one. If NO, or if
* there is no queue, a new queue containing only the selected item is created.
*/
func loadSelectedItem(byAppending appending: Bool) {
print("enqueue item \(String(describing: mediaInfo))")
if let remoteMediaClient = sessionManager.currentCastSession?.remoteMediaClient {
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = true
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
let mediaQueueItem = mediaQueueItemBuilder.build()
if appending {
let request = remoteMediaClient.queueInsert(mediaQueueItem, beforeItemWithID: kGCKMediaQueueInvalidItemID)
request.delegate = self
} else {
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = remoteMediaClient.mediaStatus?.queueRepeatMode ?? .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.mediaInformation = mediaInfo
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = remoteMediaClient.loadMedia(with: mediaLoadRequestDataBuilder.build())
request.delegate = self
}
}
}
...
}
Now update various existing methods to use the Cast Session logic to support remote playback:
required init?(coder: NSCoder) {
super.init(coder: coder)
...
castMediaController = GCKUIMediaController()
...
}
func switchToLocalPlayback() {
print("switchToLocalPlayback")
if playbackMode == .local {
return
}
setQueueButtonVisible(false)
var playPosition: TimeInterval = 0
var paused: Bool = false
var ended: Bool = false
if playbackMode == .remote {
playPosition = castMediaController.lastKnownStreamPosition
paused = (castMediaController.lastKnownPlayerState == .paused)
ended = (castMediaController.lastKnownPlayerState == .idle)
print("last player state: \(castMediaController.lastKnownPlayerState), ended: \(ended)")
}
populateMediaInfo((!paused && !ended), playPosition: playPosition)
sessionManager.currentCastSession?.remoteMediaClient?.remove(self)
playbackMode = .local
}
func switchToRemotePlayback() {
print("switchToRemotePlayback; mediaInfo is \(String(describing: mediaInfo))")
if playbackMode == .remote {
return
}
// If we were playing locally, load the local media on the remote player
if playbackMode == .local, (_localPlayerView.playerState != .stopped), (mediaInfo != nil) {
print("loading media: \(String(describing: mediaInfo))")
let paused: Bool = (_localPlayerView.playerState == .paused)
let mediaQueueItemBuilder = GCKMediaQueueItemBuilder()
mediaQueueItemBuilder.mediaInformation = mediaInfo
mediaQueueItemBuilder.autoplay = !paused
mediaQueueItemBuilder.preloadTime = TimeInterval(UserDefaults.standard.integer(forKey: kPrefPreloadTime))
mediaQueueItemBuilder.startTime = _localPlayerView.streamPosition ?? 0
let mediaQueueItem = mediaQueueItemBuilder.build()
let queueDataBuilder = GCKMediaQueueDataBuilder(queueType: .generic)
queueDataBuilder.items = [mediaQueueItem]
queueDataBuilder.repeatMode = .off
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
mediaLoadRequestDataBuilder.queueData = queueDataBuilder.build()
let request = sessionManager.currentCastSession?.remoteMediaClient?.loadMedia(with: mediaLoadRequestDataBuilder.build())
request?.delegate = self
}
_localPlayerView.stop()
_localPlayerView.showSplashScreen()
setQueueButtonVisible(true)
sessionManager.currentCastSession?.remoteMediaClient?.add(self)
playbackMode = .remote
}
/* Play has been pressed in the LocalPlayerView. */
func continueAfterPlayButtonClicked() -> Bool {
let hasConnectedCastSession = sessionManager.hasConnectedCastSession
if mediaInfo != nil, hasConnectedCastSession() {
// Display an alert box to allow the user to add to queue or play
// immediately.
if actionSheet == nil {
actionSheet = ActionSheet(title: "Play Item", message: "Select an action", cancelButtonText: "Cancel")
actionSheet?.addAction(withTitle: "Play Now", target: self,
selector: #selector(playSelectedItemRemotely))
}
actionSheet?.present(in: self, sourceView: _localPlayerView)
return false
}
return true
}
Now, run the app on your mobile device. Connect to your Cast device and start playing a video. You should see the video playing on the receiver.
7. Mini controller
The Cast Design Checklist requires that all Cast apps provide mini controller to appear when the user navigates away from the current content page. The mini controller provide instant access and a visible reminder for the current Cast session.
The Cast SDK provides a control bar, GCKUIMiniMediaControlsViewController
, which can be added to the scenes in which you want to show the persistent controls.
For the sample app, we are going to use the GCKUICastContainerViewController
which wraps another view controller and adds a GCKUIMiniMediaControlsViewController
at the bottom.
Modify the AppDelegate.swift
file and add the following code for if useCastContainerViewController
condition in the following method:
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
guard let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
as? UINavigationController else { return false }
let castContainerVC = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
as GCKUICastContainerViewController
castContainerVC.miniMediaControlsItemEnabled = true
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = castContainerVC
window?.makeKeyAndVisible()
...
}
Add this property and setter/getter to control the visibility of the mini controller (we will use these in a later section):
var isCastControlBarsEnabled: Bool {
get {
if useCastContainerViewController {
let castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
return castContainerVC!.miniMediaControlsItemEnabled
} else {
let rootContainerVC = (window?.rootViewController as? RootContainerViewController)
return rootContainerVC!.miniMediaControlsViewEnabled
}
}
set(notificationsEnabled) {
if useCastContainerViewController {
var castContainerVC: GCKUICastContainerViewController?
castContainerVC = (window?.rootViewController as? GCKUICastContainerViewController)
castContainerVC?.miniMediaControlsItemEnabled = notificationsEnabled
} else {
var rootContainerVC: RootContainerViewController?
rootContainerVC = (window?.rootViewController as? RootContainerViewController)
rootContainerVC?.miniMediaControlsViewEnabled = notificationsEnabled
}
}
}
Run the app and cast a video. When playback starts on the receiver you should see the mini controller appear at the bottom of each scene. You can control the remote playback using the mini controller. If you navigate between the browse activity and the local player activity, the mini controller state should stay in sync with the receiver media playback status.
8. Introductory overlay
The Google Cast design checklist requires a sender app to introduce the Cast button to existing users to let them know that the sender app now supports Casting and also helps users new to Google Cast.
The GCKCastContext
class has a method, presentCastInstructionsViewControllerOnce
, that can be used to highlight the Cast button when it is first shown to users. Add the following code to MediaViewController.swift
and MediaTableViewController.swift
:
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self, selector: #selector(castDeviceDidChange),
name: NSNotification.Name.gckCastStateDidChange,
object: GCKCastContext.sharedInstance())
}
@objc func castDeviceDidChange(_: Notification) {
if GCKCastContext.sharedInstance().castState != .noDevicesAvailable {
// You can present the instructions on how to use Google Cast on
// the first time the user uses you app
GCKCastContext.sharedInstance().presentCastInstructionsViewControllerOnce(with: castButton)
}
}
Run the app on your mobile device and you should see the introductory overlay.
9. Expanded controller
The Google Cast design checklist requires a sender app to provide expanded controller for the media being cast. The expanded controller is a full screen version of the mini controller.
The expanded controller is a full screen view which offers full control of the remote media playback. This view should allow a casting app to manage every manageable aspect of a cast session, with the exception of receiver volume control and session lifecycle (connect/stop casting). It also provides all the status information about the media session (artwork, title, subtitle, and so forth).
The functionality of this view is implemented by the GCKUIExpandedMediaControlsViewController
class.
The first thing you have to do is enable the default expanded controller in the cast context. Modify AppDelegate.swift
to enable the default expanded controller:
import GoogleCast
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
// Add after the setShareInstanceWith(options) is set.
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true
...
}
...
}
Add the following code to MediaViewController.swift
to load the expanded controller when the user starts to cast a video:
@objc func playSelectedItemRemotely() {
...
appDelegate?.isCastControlBarsEnabled = false
GCKCastContext.sharedInstance().presentDefaultExpandedMediaControls()
}
The expanded controller will also be launched automatically when the user taps the mini controller.
Run the app and cast a video. You should see the expanded controller. Navigate back to the list of videos and when you click on the mini controller, the expanded controller will be loaded again.
10. Add Cast Connect support
Cast Connect library allows existing sender applications to communicate with Android TV applications via the Cast protocol. Cast Connect builds on top of the Cast infrastructure, with your Android TV app acting as a receiver.
Dependencies
In your Podfile
, make sure the google-cast-sdk
is pointed to 4.4.8
or higher as listed below. If you made a modification to the file, run pod update
from the console to sync the change with your project.
pod 'google-cast-sdk', '>=4.4.8'
GCKLaunchOptions
In order to launch the Android TV application, also referred to as the Android Receiver, we need to set the androidReceiverCompatible
flag to true in the GCKLaunchOptions
object. This GCKLaunchOptions
object dictates how the receiver is launched and is passed to the GCKCastOptions
which are set in the shared instance using GCKCastContext.setSharedInstanceWith
.
Add the following lines to your AppDelegate.swift
:
let options = GCKCastOptions(discoveryCriteria:
GCKDiscoveryCriteria(applicationID: kReceiverAppID))
...
/** Following code enables CastConnect */
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
Set Launch Credentials
On the sender side, you can specify GCKCredentialsData
to represent who is joining the session. The credentials
is a string which can be user-defined, as long as your ATV app can understand it. The GCKCredentialsData
is only passed to your Android TV app during launch or join time. If you set it again while you are connected, it won't be passed to your Android TV app.
In order to set Launch Credentials GCKCredentialsData
needs to be defined anytime after the GCKLaunchOptions
are set. To demonstrate this, let's add logic for the Creds button to set credentials to be passed along when the session is established. Add the following code to your MediaTableViewController.swift
:
class MediaTableViewController: UITableViewController, GCKSessionManagerListener, MediaListModelDelegate, GCKRequestDelegate {
...
private var credentials: String? = nil
...
override func viewDidLoad() {
...
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Creds", style: .plain,
target: self, action: #selector(toggleLaunchCreds))
...
setLaunchCreds()
}
...
@objc func toggleLaunchCreds(_: Any){
if (credentials == nil) {
credentials = "{\"userId\":\"id123\"}"
} else {
credentials = nil
}
Toast.displayMessage("Launch Credentials: "+(credentials ?? "Null"), for: 3, in: appDelegate?.window)
print("Credentials set: "+(credentials ?? "Null"))
setLaunchCreds()
}
...
func setLaunchCreds() {
GCKCastContext.sharedInstance()
.setLaunch(GCKCredentialsData(credentials: credentials))
}
}
Set Credentials on Load Request
In order to handle credentials
on both your Web and Android TV Receiver apps, add the following code in your MediaTableViewController.swift
class under loadSelectedItem
function:
let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder()
...
mediaLoadRequestDataBuilder.credentials = credentials
...
Depending on the receiver app your sender is casting to, the SDK would automatically apply the above credentials to the ongoing session.
Testing Cast Connect
Steps to install the Android TV APK on Chromecast with Google TV
- Find the IP Address of your Android TV device. Usually, it's available under Settings > Network & Internet > (Network name your device is connected to) . On the right hand it will show the details and your device's IP on the network.
- Use the IP address for your device to connect to it via ADB using the terminal:
$ adb connect <device_ip_address>:5555
- From your terminal window, navigate into the top level folder for the codelab samples that you downloaded at the start of this codelab. Например:
$ cd Desktop/ios_codelab_src
- Install the .apk file in this folder to your Android TV by running:
$ adb -s <device_ip_address>:5555 install android-tv-app.apk
- You should now be able to see an app by the name of Cast Videos in the Your Apps menu on your Android TV device.
- Once done, build and run the app on an emulator or a mobile device. On establishing a cast session with your Android TV device, it should now launch the Android Receiver application on your Android TV. Playing a video from your iOS mobile sender, should launch the video in the Android Receiver and allow you to control playback using the remote for your Android TV device.
11. Customize Cast widgets
Initialization
Start with the App-Done folder. Add the following to the applicationDidFinishLaunchingWithOptions
method in your AppDelegate.swift
file.
func application(_: UIApplication,
didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
...
let styler = GCKUIStyle.sharedInstance()
...
}
Once you are done applying one or more customizations as mentioned in the rest of this codelab, commit the styles by calling the code below
styler.apply()
Customizing Cast views
You can customize all views that the Cast Application Framework manages by having default styling guidelines across views. As an example, let's change the icon tint color.
styler.castViews.iconTintColor = .lightGray
You can override defaults on a per-screen basis if required. For example, to override the lightGrayColor for the icon tint color just for the expanded media controller.
styler.castViews.mediaControl.expandedController.iconTintColor = .green
Changing colors
You can customize the background color for all views (or individually for each view). The following code sets the background color to blue for all your Cast Application Framework provided views.
styler.castViews.backgroundColor = .blue
styler.castViews.mediaControl.miniController.backgroundColor = .yellow
Changing fonts
You can customize fonts for different labels seen within cast views. Let's set all fonts to 'Courier-Oblique' for illustration purposes.
styler.castViews.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 16) ?? UIFont.systemFont(ofSize: 16)
styler.castViews.mediaControl.headingTextFont = UIFont.init(name: "Courier-Oblique", size: 6) ?? UIFont.systemFont(ofSize: 6)
Changing default button images
Add your own custom images to the project, and assign the images to your buttons to style them.
let muteOnImage = UIImage.init(named: "yourImage.png")
if let muteOnImage = muteOnImage {
styler.castViews.muteOnImage = muteOnImage
}
Changing the Cast button theme
You can also theme Cast Widgets using the UIAppearance Protocol. The following code themes the GCKUICastButton on all the views it appears:
GCKUICastButton.appearance().tintColor = UIColor.gray
12. Congratulations
You now know how to Cast-enable a video app using the Cast SDK widgets on iOS.
For more details, see the iOS Sender developer guide.