MigrationGuideV3

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

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