Migration Guide from v3
PRESTOplay SDK for Apple version 4 has been re-designed from the ground up to build a faster and smaller player SDK. We focused on the core components of video playback and the underlying media playback engine.
The result is a small, performant core that can be easily integrated in any Application Environment. Skinning the player and building an interface can be done with your favorite UI framework without any workarounds and limitations.
Since the new architecture was only possible with drastic measures, we needed to adopt and change the previous API. The following guide is intended to enable a smooth migration from our version 3.x to version 4.x of our SDK and highlight some of the key differences.
Table of Contents
- Player Initialization
- Plugins
- Loading Content
- Attaching Event Listeners
- DRM
- Plugin Configuration
- Track Management
- Thumbnails
- Analytics
- Reset player
- Metadata
Player Initialization
The minimal required configuration to initialize the SDK remains your castLabs license. In version 4, we utilize the singleton pattern and expose the shared
property from PRESTOplay SDK, which differs from the factory pattern used on the previous version.
Version 3
// init sdk
[CastlabsSDK with:@[]
andLicenseKey:@"LICENSE"
andDelegate:nil];
// get player
CLPlayerFactory* factory = [[CLPlayerFactory alloc] init];
CLPlayer* player = [factory createPlayerWithStreamUrl:<stream_url>
andContentType:<content_type>];
Version 4
// init sdk
_ = PRESTOplaySDK.shared.register("LICENSE", [])
// get player
let player = PRESTOplaySDK.shared.player()
Plugins
The Apple SDK provides plugins which, added to the player, enable playback of different types of content and/or add new functionality to the player. The plugins are self-contained bundles that add a specific capability to the core player and also provide integrations with third party libraries or services. Below is an example on how to use HLSPlugin
to enable playback of HLS content.
Version 3
// init sdk
[CastlabsSDK with:@[<other_plugins>]
andLicenseKey:@"LICENSE"
andDelegate:nil];
// get player
CLPlayerFactory* factory = [[CLPlayerFactory alloc] init];
CLPlayer* player = [factory createPlayerWithStreamUrl:<stream_url>
andContentType:kContentHLS];
Version 4
// init sdk
_ = PRESTOplaySDK.shared.register("LICENSE", [HLSPlugin(),])
// get player
let player = PRESTOplaySDK.shared.player()
Loading Content
Version 3
// init sdk
[CastlabsSDK with:@[<other_plugins>]
andLicenseKey:@"LICENSE"
andDelegate:nil];
CLPlayerFactory* factory = [[CLPlayerFactory alloc] init];
// get a player for the given stream url
CLPlayer* player = [factory createPlayerWithStreamUrl:<stream_url>
andContentType:<content_type>];
// or
CLPlayer* player = [factory createPlayerWithStreamUrl:<stream_url>
andDrmConfiguration:<drm_configuration>
andContentType:<content_type>];
// or
CLPlayer* player = [factory createPlayerWithStreamUrl:<stream_url>
andDrmConfiguration:<drm_configuration>
andContentMetadata:<content_metadata>
andContentType:<content_type>];
Version 4
let contentUrl = URL(string: "<stream_url>")!
var config = PlayerConfiguration(with: contentUrl)
// set metadata/drm configuration
config.drmType = .drmToday
config.drmSystem = .fairplay
config.drmConfiguration = DRMConfiguration() // ...
config.metaData = MetaData() // ...
// ...
let player = PRESTOplaySDK.shared.player()
player.load(config: config)
Attaching Event Listeners
Version 3
// the view controller implements `CLPlayerListenerProtocol`
@interface PlayerViewController() <CLPlayerListenerProtocol> {}
@end
@implementation PlayerViewController
// ...
- (void) onStateChangedTo:(StateID)newState from:(StateID)oldState withData:(NSDictionary*)data {
// ...
}
@end
Version 4
class PlayerViewController: UIViewController {
var player = PRESTOplaySDK.shared.player()
let contentUrl = URL(string: "<stream_url>")!
var config = PlayerConfiguration(with: contentUrl)
// ...
player.attach(to: view.layer)
player.onState = { [weak self] previous, state in
guard let self else { return }
if let error = player.error {
print("Error \(error)")
}
}
player.load(config: config)
}
DRM
Version 3
CLDrmConfiguration * drmConfig = [[CLDrmConfiguration alloc] init];
drmConfig.assetId = @"...";
drmConfig.merchantId = @"...";
drmConfig.sessionId = @"...";
drmConfig.userId = @"...";
drmConfig.environment = kProduction;
drmConfig.type = kDrmFairplay;
CLPlayer* player = [factory createPlayerWithStreamUrl:@"https://example.com/main.m3u8"
andDrmConfiguration:drmConfig
andContentType:kContentHLS];
// ...
[self.player open];
Version 4
var player = PRESTOplaySDK.shared.player()
let contentUrl = URL(string: "https://example.com/main.m3u8")!
var drmConfig = DRMConfiguration()
drmConfiguration.environment = .production
drmConfiguration.userId = "..."
drmConfiguration.sessionId = "..."
drmConfiguration.merchant = "..."
drmConfiguration.assetId = "..."
drmConfiguration.environment = "..."
var config = PlayerConfiguration(with: contentUrl)
config.drmType = .drmToday
config.drmSystem = .fairplay
config.drmConfiguration = drmConfig // ...
config.metaData = MetaData() // ...
// ...
player.load(config: config)
Plugin Configuration
Version 3
NSMutableArray *plugins = [NSMutableArray arrayWithObjects:
[CastlabsApple alloc],
nil];
// e.g. init youbora plugin
[plugins addObject:[[CastlabsYoubora alloc] initWithKeys:@{
@"accountCode": @"<account_code>"
}]];
[CastlabsSDK with:plugins
andLicenseKey:@"LICENSE"
andDelegate:nil];
Version 4
var plugins: [CLPluginProtocol] = [HLSPlugin(),]
// init youbora plugin
let youboraSettings = YouboraSettings(accountCode: "<account_code>")
plugins.append(YouboraPlugin(youboraSettings))
// init sdk
_ = PRESTOplaySDK.shared.register("LICENSE", plugins)
// get player
let player = PRESTOplaySDK.shared.player()
Track Management
Version 3
int nrAudioTracks = [player numberOfAudioTracks];
NSMutableArray *audioTracks = [NSMutableArray array];
for (int i=0; i < nrAudioTracks; i++) {
CLPlayerAudioTrack *audioTrack = [player describeAudioTrack:i];
[audioTracks addObject:audioTrack];
}
// ...
Version 4
let audioTracks: [AudioTrack] = player.tracks.audio
// ...
Track Model
One of the significant changes in Version 4 is around the track model. The new track model now differentiates between tracks and renditions. A track now represents a set of streams of a particular type (video, audio, text). A track contains one or many renditions. A rendition is a specific stream for a target quality level or bitrate.
Track Information
Version 3
int nrAudioTracks = [player numberOfAudioTracks];
NSMutableArray *audioTracks = [NSMutableArray array];
for (int i=0; i < nrAudioTracks; i++) {
CLPlayerAudioTrack *audioTrack = [player describeAudioTrack:i];
[audioTracks addObject:audioTrack];
NSLog(@"AudioTracks, codec: %@ index: %ld", audioTrack.codec, (long)audioTrack.trackIndex);
}
// ...
Version 4
var tracks: [Track] = player.tracks.all
var videoTracks: [VideoTrack] = player.tracks.video
var selectedVideoTrack = player.getVideoTrack()
var selectedVideoRendition = player.getSelectedVideoRendition()
print("Selected video rendition, codec: \(selectedVideoRendition.codec)")
for track in tracks {
// print track properties ...
print("Track info: \(track.label) ...")
}
Track Selection
Version 3
// get track index from player API
// e.g. int audioIndex = 1 ...
// ...
player.selectAudio(audioIndex);
player.selectVideo(videoIndex);
player.selectText(textIndex);
Version 4
let videoTrack = player.tracks.video.first!
let audioTrack = player.tracks.audio.first!
let textTrack = player.tracks.text.first!
// ...
player.setVideoTrack(videoTrack)
player.setAudioTrack(audioTrack)
player.setTextTrack(textTrack)
Sideloaded Text Tracks
Version 3
[player addSubtitlesTrackFromUrl:[NSURL URLWithString:@"<subtitles_url>"]
withFormat:kSubtitleWebVTT
withDisplayName:@"Sideloaded subtitles"
withLanguageCode:@"en"];
// use player APIs to select the sideloaded track ...
Version 4
// ...
let subtitles = PRESTOplaySDK.shared.subtitles(for: player)
let textTrack = TextTrack(
id: UUID().uuidString,
format: .web_vtt,
isPlatformRendered: false,
label: "Sideloaded subtitles",
language: "en",
sourceUrl: URL(string: "<subtitles_url>")!)
subtitles?.addTextTrack(textTrack, completionHandler: { error in
if let error {
print(error)
} else {
print("subtitles track loaded")
}
DispatchQueue.main.async {
self.player.setTextTrack(subtitlesTrack)
}
})
Thumbnails
Version 3
CLPlayer* player = // ...
[player setThumbnailWithWebVTTtrack:@"https://example.com/thumbs.vtt"];
// Get a thumbnail at position 12 (seconds)
CLThumb* thumbnail = [player getThumb: CMTimeMakeWithSeconds(12, 1)];
// ...
Version 4
let player = PRESTOplaySDK.shared.player()
let thumbnails = PRESTOplaySDK.shared.thumbnails(for: player)
let thumbnailsUrl = URL(string: "https://example.com/thumbs.vtt")!
thumbnails?.loadThumbnailsFrom(webVTTtrack: thumbnailsUrl, completionHandler: { error in
if let error { print(error) } else { print("thumbnails loaded") }
})
// ...
let timestamp = CMTime(seconds: 12, preferredTimescale: 1)
let thumbnail = thumbnails?.getThumbnail(atTimestamp: timestamp)
let image = UIImage(data: thumbnail.imageData)
Analytics
An example of using Youbora analytics plugin and how to pass content metadata.
Version 3
CLContentMetadata* metadata = [[CLContentMetadata alloc] initWithContentAssetId:@"asset_1" title:@"Test Stream" isLive:NO];
CLPlayerFactory* factory = [[CLPlayerFactory alloc] init];
// pass the content metadata to the factory method
CLPlayer* player = [factory createPlayerWithStreamUrl:@"<stream_url>"
andDrmConfiguration:<drm_config>
andContentMetadata:metadata
andContentType:kContentHLS];
Version 4
let player = PRESTOplaySDK.shared.player()
// ...
var analytics = PRESTOplaySDK.shared.analytics(for: player, .youbora)
let youboraMetadata = YouboraMetadata(
live: false,
assetId: "assetId")
youboraMetadata.analyticsOptions = NpawPlugin.AnalyticsOptions()
youboraMetadata.analyticsOptions?.adCustomDimension1 = "test"
youboraMetadata.analyticsOptions?.contentTitle = "test"
youboraMetadata.accountCode = "ACCOUNT_CODE"
analytics?.metadata = youboraMetadata
Reset player
To achieve the same behavior as reset()
in V3, use the load(config:)
method. To change the starting position, set the startTimeMs
value.
Version 3
[self.player reset:YES];
Version 4
let player = PRESTOplaySDK.shared.player()
player.load(config: playerConfig)
// ...
playerConfig.startTimeMs = 10000
player.load(config: playerConfig)
Metadata
In V4, CLContentMetadata
from V3 is converted to AnalyticsMetadata
. And it is set on the plugin instance instead of player.
Version 3
player.metadata = [[CLContentMetadata alloc] initWithContentAssetId:config.identifier title:config.metadata.title isLive:config.live];
player.metadata.imageUrl = config.metadata.thumbUrl;
player.metadata.imageWidht = 173;
player.metadata.imageHeight = 256;
player.metadata.viewerId = "viewer id";
Version 4
Configure the metadata for Conviva plugin. It is similar for other plugins.
var analytics = PRESTOplaySDK.shared.analytics(for: player, .conviva)
let metadata = ConvivaMetadata( live: false, assetId: "assetId")
metadata.assetName = "asset name"
analytics.metadata = metadata
Configure the metadata for Chromecast plugin.
import CastlabsChromecast
import GoogleCast
let castSettings = CastSettings(appId)
var cast = PRESTOplaySDK.shared.cast(for: castSettings)
let metadata = ChromecastMetadata(live: false, assetId: "assetId")
metadata.title = "title"
metadata.subtitle = "subtitle"
metadata.mediaMetadata = GCKMediaMetadata()
// Set mediaMetadata properties.
// ...
cast?.metadata = metadata