tvOS SDK
This guide explains how to integrate DIVA into your tvOS application.
A VXP GitHub account is required to gain access to:
- Basic tvOS integration tagged by SDK versions.
Requirements​
DIVA requires a minimum of XCode 15 and tvOS 15.0.
Diva tvOS uses DivaCore
package to share functionality with Diva iOS and DivaTVOSDefinitions
package for definitions related to Diva tvOS only.
Provisioning​
In order to initialize and use a DIVA player instance, It requires the provision of three configuration objects.
Settings
, with which DIVA will be configured.Dictionary
, which will provide DIVA with the translations and localisations for the application.VideoMetaDataProvider
, which provides DIVA with information about the playback for a specific item.
This data is provided to Diva via DivaTVConfiguration
struct.
More information below:
Settings​
Settings
consists of a data structure that can be used to setup, activate/deactivate, and adapt DIVA's UX and feature behaviours to fit project-specific integration needs.
SWSettingCleanModel
is a common interface for Diva iOS and tvOS. At the moment not all the features currently available in iOS are in tvOS. Settings available for customization are:
SWSettingCleanModel/pushEngine
SWSettingCleanModel/customPlayByPlay
SWSettingCleanModel/highlights
- In
SWSettingCleanModel/general
onlySWGeneralCleanModel/closedCaptionSelectionMethod
,SWGeneralCleanModel/closedCaptionSelectionMethod
,SWGeneralCleanModel/culture
,SWGeneralCleanModel/audioSelectionMethod
andSWGeneralCleanModel/videoAnalyticsEventFrequency
.
Below is a basic sample implementation:
let settings = SWSettingCleanModel.make(general:SWGeneralCleanModel.make(culture: "en-GB"))
For more information about settings and the available options click here.
Dictionary​
Dictionary
is a map of key/value pairs corresponding to the different localization strings used to populate Diva user interface. Some translations are provided by tvOS. The language which will be displayed to end users depends on 2 things:
- Your host application supported languages
- Language which the user has set up in his/her Apple TV
For instance if the host app only supports English (default/base) and French, and the user has his/her Apple TV language in Spanish. The user will see the player UI in English since Spanish is not supported but in English because it is the default language for the host application. If the user has French selected in his/her Apple TV, then the players' language will be French. Sample interfaces provided by tvOS are the audio languages list, the subtitles list and the info panel title.
Below a sample implementation:
let dictionary_example = SWDictionaryCleanModel(messages: [
"diva_live": "Live Now",
"diva_go_live": "Go Live",
"diva_video_error": "This video is not working or not available in your region.",
"diva_error_button_ok": "OK",
])
For more information about dictionary and the available options click here.
VideoMetaData​
videoMetadataProvider
is used by Diva Player to request VideoMetadataResult
. Your implementation is passed to Diva via DivaTVConfiguration
. This will allow DIVA to request provision of data as and when needed based on the Video ID.
public protocol VideoMetadataProvider {
/// Diva Player uses this function to get Video Metadata to play
/// - Parameters:
/// - videoId: Unique id of the video to be open
/// - currentVideoMetadata: If diva has a currently use VideoMetadata it will be send here. For instance if this is the first time calling the function then it will be nil
/// - playbackState: Current state which is needed
/// - onMetadataCallback: Response to diva player
func requestVideoMetadata(videoId: String,
currentVideoMetadata: SWVideoMetadataCleanModel?,
playbackState: PlaybackState,
onMetadataCallback: @escaping (VideoMetadataResult) -> Void)
}
There is no limitation as to how this provision can be implemented, in a live environment a remote endpoint may be needed, but a simple hard coded conversion of VideoMetaData is as follows:
struct ExampleVideoMetadataProvider: VideoMetadataProvider {
func requestVideoMetadata(
videoId: String,
currentVideoMetadata: DivaCore.SWVideoMetadataCleanModel?,
playbackState: PlaybackState,
onMetadataCallback: @escaping (VideoMetadataResult) -> Void
) {
onMetadataCallback(
generateVideoMetadataResponse(videoId: videoId)
)
}
private func generateVideoMetadataResponse(videoId: String) -> VideoMetadataResult {
switch videoId {
case "videoId_fravscro":
return .success(fravscro)
default:
return .failure(.noData)
}
}
private let fravscro = SWVideoMetadataCleanModel.make(
videoId: "videoId_fravscro",
title: "France Vs Croatia",
eventId: "108606",
programDateTime: "2018-07-15T13:40:15.681Z",
trimIn: 4_680_858,
trimOut: 11_641_166,
assetState: .vod,
ad: "https://divademo.deltatre.net/DIVAProduct/www/Data/Vast/skippable2.xml",
sources: [
SWVideoSourceCleanModel.make(
uri: "https://vod-ffwddevamsmediaservice.streaming.mediaservices.windows.net/6e8b64ef-feb3-4ea9-9c63-32ef7b45e1dd/6e8b64ef-feb3-4ea9-9c63-32ef7b45.ism/manifest(format=m3u8-aapl,filter=hls)",
format: "HLS"),
],
dvrType: .full
)
}
Initialization​
Once the necessary data has been provisioned, a DIVA object is simple to instantiate through the creation of a DivaTVConfiguration
with the relevant Settings, Dictionary and VideoMetaData provision.
After initializing Diva by using init(configuration:)
, it is necessary to present DivaTV.playerViewController
. Alternativelly you can add DivaTV.playerViewController
as a child ViewController in a ViewController and then present that ViewController.
Below is a sample implementation:
func didPress() async {
let videoMetadataProvider = ExampleVideoMetadataProvider()
let settingsCleanModel = SWSettingCleanModel.make(general:SWGeneralCleanModel.make(culture: "en-GB"))
let dictionary = SWDictionaryCleanModel(messages: [
"diva_live": "Live Now",
"diva_go_live": "Go Live",
"diva_error_title": "Error",
"diva_video_error": "This video is not working or not available in your region.",
"diva_error_button_ok": "OK",
])
var divaConfiguration = DivaTVConfiguration(
dictionary: dictionary,
settings: settingsCleanModel,
videoId: "videoId_fravscro",
videoMetadataProvider: videoMetadataProvider
)
divaConfiguration.onVideoError = { error, videoMetadata in
print("[TestApp-onVideoError] Error: \(error) \n metadata: \(String(describing: videoMetadata))")
}
let initializedDiva = await DivaTV(configuration: divaConfiguration)
present(initializedDiva.playerViewController, animated: true)
}
DivaConfiguration
fields​
logLevel
​
Logger messages minimal level. Available values are debug
, info
, notice
(default), error
and fault
. DivaLogger uses Apple's logging underneath which allows you to log some levels into device and recover it if needed. Besides diva configuration, DivaLogger is also present in plugins.
embedMode
​
You case use the followign values
fullscreen
- if you want to display Diva player on the whole device screen.chromeless(loopback: Bool)
- if you want to embed Diva player into a view as a subview. No player controls are visible. Setloopback
totrue
if you want to restart playback on end.
DivaLogger
​
Besides Diva configuration, DivaLogger is also present in plugins. For instance in Convvia plug in use DivaLogger
to log events.
API​
Available from version 5.5.
After you have initialized Diva, Diva exposes an interface to communicate which can alter its behaviour in runtime. Access it via DivaTV/api
Remember that other way to customize Diva player is via SWVideoMetadataCleanModel
. It is passed via VideoMetadataProvider
.
DIVA API reference list:
/// Interface to communicate with Diva Player
public protocol PlayerAPI: AnyObject {
/// Timeline icon threshold consider to group colliding icons. Grouped icons are displayed in Timeline and highlights.
/// - Parameter distance: Value is express in points. A valid value is a positive number greater than or equal to 1. If 0, no grouping is applied. If negative value then default is applied.
func setTimelineIconsMinimalDistance(distance: Int) async
/// Sends Diva Player a command
/// - Parameter command: command to execute
func sendPlayerCommand(_ command: PlayerApiModel.Command) async
/// Returns DIVA session. It represent the lifecycle of a DIVA player instance.
/// Every time a DIVA player is instantiated, a session id is created, and discarded once DIVA player is removed from memory.
/// - Returns: String with the Diva session identifier
func getSessionId() async -> String
/// Returns current player state
/// - Returns: Player state
func getPlayerState() async -> PlayerApiModel.State
/// Returns a publisher with the current player state, initial state stoped
/// - Returns: publisher
func getPlayerStatePublisher() async -> AnyPublisher<PlayerApiModel.State, Never>
/// Returns current player position
/// - Returns: current player position
func getPlayerPosition() async -> PlayerApiModel.Position
/// Returns a publisher which returns an updated value of the position
/// - Returns: Publisher
func getPlayerPositionPublisher() async -> AnyPublisher<PlayerApiModel.Position, Never>
/// Returns the current Video metadata
/// - Returns: current video metadata, can be nil in case of error or before it arrives for the first time
func getVideoMetadata() async -> SWVideoMetadataCleanModel?
/// Publisher which returns the current video metadata
/// - Returns: publisher
func getVideoMetadataPublisher() async -> AnyPublisher<SWVideoMetadataCleanModel?, Never>
/// Publisher which returns the current media duration
/// - Returns: publisher
func getMediaDurationPublisher() async -> AnyPublisher<Double?, Never>
/// Sends a media analytical action to the player
/// - Parameter MediaAnalyticAction: Analytical action
/// Diva uses this value to send a media analytical event via ``DivaConfiguration.onMediaAnalyticsUpdate`` for iOS or ``DivaTVConfiguration.onMediaAnalyticsUpdate`` for tvOS.
/// This value does not alter Diva's behavior.
func sendEvent(mediaAnalyticAction: MediaAnalyticsUpdate.Action) async
/// Real time when video started.
var videoAbsoluteStartTime: Date? { get async }
/// Is player currently seeking to a new position or not.
var isSeeking: Bool { get async }
}
public enum PlayerApiModel {
/// List of Commands which can be passed to Diva
public enum Command: Equatable, Sendable, CustomStringConvertible {
/// Mutes current video
case mute(value: Bool)
/// Pauses current video
case pause
/// Plays current video
case play
/// Changes playback rate
case playbackRate(value: Float)
/// Seek to a relative position
case seek(value: Double)
/// Seek to an specific date, takes into account ``SWVideoMetadataCleanModel.programDateTime``
case seekAbsolute(value: Date)
/// Changes the audio playback volume for the player.
/// A value of 0.0 indicates silence; a value of 1.0 (the default) indicates full audio volume for the player instance.
/// This property is used to control the player audio volume relative to the system volume.
case volume(value: Float)
public var description: String {
switch self {
case .play: return "play"
case .pause: return "pause"
case .mute(let value): return "mute: \(value)"
case .seek(let value): return "seek: \(value)"
case .seekAbsolute(let value): return "seekAbsolute: \(value)"
case .playbackRate(let value): return "playbackRate: \(value)"
case .volume(let value): return "volume: \(value)"
}
}
}
/// Diva Player state
public enum State: Equatable, Sendable, CustomStringConvertible {
/// Player is not initialized yet or is about to close
case stopped
// Playback is paused
case paused
// Playing content
case playing
// Playback has failed
case playbackFailed
public var description: String {
switch self {
case .stopped: return "stopped"
case .paused: return "paused"
case .playing: return "playing"
case .playbackFailed: return "playback failed"
}
}
}
/// Returns current player position
public struct Position: Equatable, Sendable {
/// Relative time in milliseconds. Takes in consideration trim-in value set in ``SWVideoMetadataCleanModel.trimIn``
public var milliseconds: Double
/// Current time expressed as Date, calculated based on ``SWVideoMetadataCleanModel.programDateTime``
public var absolute: Date?
public init(milliseconds: TimeInterval, absolute date: Date? = nil) {
self.milliseconds = milliseconds
absolute = date
}
}
}
Extra configuration​
Configuring the Audio Playback​
Apps integrating DivaTVOS are responsable for configuring the audio playback (See Apple's documentation). It is recommended to use the default settings when possible, similar to Apple's suggestion:
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)