BlendVisionLoomPlayer is a Swift library to help you integrate BlendVision Loom player into your iOS app.
To have a quick look at the features of BlendVisionLoomPlayer SDK, try our sample app by following the steps below
- Clone or download this repository.
- Open the
sample/BlendVisionLoomPlayerSample.xcodeproj
file in Xcode. - Build and run the
BlendVisionLoomPlayerSample
scheme.
- Xcode 13.1
- iOS 12.0 (latest 3 versions)
- Swift 5.0
Note
BlendVisionLoomPlayer will drop support for older OS three months after the latest version is officially released by Apple. For example, iOS 13 was released on
Sep 19, 2019
, then iOS 10 support would be dropped afterDec 20, 2019
.
To use BlendVisionLoomPlayer within your app, following the steps below
- Copy ALL framework files under the
frameworks
folder into your project folder. - Add your framework folder's path to "Build Settings > Framework Search Paths".
- Add relevant frameworks to the "Frameworks, Libraries, and Embedded Content" section of "General" tab in your app target.
- BlendVisionLoomPlayer.xcframework (Embed & Sign)
- KKSPaaS.xcframework (Embed & Sign)
- KKSNetwork.xcframework (Embed & Sign)
- KKSLocalization.xcframework (Embed & Sign)
- Make sure GoogleCast.framework and GoogleInteractiveMediaAds.framework are NOT added to "Frameworks, Libraries, and Embedded Content" but exist in the framework folder mentioned in step 2.
import BlendVisionLoomPlayer
let content = VideoContent(id: "VIDEO_CONTENT_ID",
title: "VIDEO_TITLE",
startTime: 0,
source: VideoContent.Source(url: VIDEO_SOURCE_URL,
thumbnailUrl: VIDEO_THUMBNAIL_URL),
drm: VideoContent.DRM(licenseUrl: DRM_LICENSE_URL,
certificateUrl: DRM_CERTIFICATE_URL,
header: DRM_HEADER)
BlendVisionLoomPlayer supports content protection with DRM/DRM+ level. You can play videos with protection by configuring license URL, FairPlay certificate URL, and a custom header for your own business logic.
// Server-to-Server
let drm = VideoContent.DRM(licenseUrl: DRM_LICENSE_URL,
certificateUrl: DRM_CERTIFICATE_URL,
header: [
// custom keys and values
"X-SESSION-ID": "MEMBER_SESSION_ID",
...
])
// Client-to-Server (NOT RECOMMENDED)
let drm = VideoContent.DRM(licenseUrl: DRM_LICENSE_URL,
certificateUrl: DRM_CERTIFICATE_URL,
header: [
"Authorization": "Bearer {JWT}"
])
// Create a player context with bar items
let context = PlayerContext(barItems: [.settings])
Use PlayerContext.BarItem.custom(view: UIView?)
to configure custom bar item.
let someButton = UIButton()
// configure the button and its action
// ...
let context = PlayerContext(barItems: [.custom(view: someButton)])
let player = LoomPlayer(context: PlayerContext(...),
contents: [VideoContent(...), ...],
startIndex: 0,
eventHandler: nil)
// Get player view.
let playerView = player.view
// Add player view onto other views.
view.addSubview(playerView)
// Layout player view.
playerView.widthAnchor.constraint(equalToConstant: 426).isActive = true
playerView.heightAnchor.constraint(equalToConstant: 240).isActive = true
BlendVisionLoomPlayer provides a quick way to present player modally with UIModalPresentationStyle.fullScreen
in a specified view controller. It is recommanded that you use this function to present one player at a time.
// If `viewController` is `nil`, the top most view controller in the view hierarchy will be used.
Loom.shared.presentPlayer(context: PlayerContext(...),
contents: [VideoContent(...), ...],
startIndex: 0,
eventHandler: nil,
in: viewController,
completion: { player in
guard let player: LoomPlayer = player else { return }
// Interact with player...
})
PlayerEventHandling
defines the methods that your handler implements to handle player events. To handle player events, you need to provide player an instance of a type that conforms to the PlayerEventHandling
protocol.
// Define a handler that conforms to PlayerEventHandling.
class PlayerEventHandler: PlayerEventHandling {
// Optional. Called when player did successfully load first video.
func didLoad(_ player: LoomPlayer) {
// Customized behaviors...
// You can pause player here to prevent player from auto-play.
player.pause()
}
// Optional. Called when player did play a video to the end.
func didEndVideo(_ player: LoomPlayer, at index: Int) {
// Customized behaviors...
// You can call `seek(to: 0)` here to replay current video.
player.seek(to: 0)
// You can call `next()` here to play next video.
player.next()
}
// Optional. Called when an error occurs.
// If this method is not implemented, default error handling (e.g. showing alerts) will be performed.
// `player` could be `nil` if error occurs before player is initialized.
func didFail(_ player: LoomPlayer?, error: LoomError) {
// Customized behaviors...
}
// Optional. Called when a video event occurs for video tracking.
func didReceiveVideoEvent(_ player: LoomPlayer?, videoEvent: VideoEvent) {
// Customized behaviors...
switch videoEvent {
case .videoPlaybackBegan(let contentIndex, let position):
break
case .videoPlaybackEnded(let contentIndex, let position):
break
case .videoPlaybackStopped(let contentIndex, let playedDuration):
break
case .videoPlaybackErrorOccurred(let contentIndex, let position, let playbackError):
break
case .play(let contentIndex, let position):
break
case .pause(let contentIndex, let position):
break
case .rewind(let contentIndex, let position):
break
case .forward(let contentIndex, let position):
break
case .previousEpisode(let contentIndex, let position):
break
case .nextEpisode(let contentIndex, let position):
break
case .videoSeekingEnded(let contentIndex, let seekFrom, let seekTo):
break
case .settingPageEntered(let contentIndex):
break
case .settingPageExited(let contentIndex):
break
}
}
}
// Provide the handler to player at initialization stage.
let handler = PlayerEventHandler()
let player = LoomPlayer(..., eventHandler: handler)
To make sure your app can receive .videoPlaybackEnded
event when app terminates, it is required to extend app's background execution time in AppDelegate
(and/or UISceneDelegate
if you support iOS 13 and later). For more details, please see Apple Developer Documentation | Extending Your App's Background Execution Time.
// AppDelegate.swift
func applicationDidEnterBackground(_ application: UIApplication) {
// Extend the app's background execution time.
// Perform the task on a background queue.
DispatchQueue.global().async {
// Request the task assertion and save the ID.
self.backgroundTaskId = UIApplication.shared.beginBackgroundTask {
// End the task if time expires.
UIApplication.shared.endBackgroundTask(self.backgroundTaskId!)
self.backgroundTaskId = UIBackgroundTaskIdentifier.invalid
}
// Do your task here...
// ...
// End the task assertion.
self.backgroundTaskId = UIBackgroundTaskIdentifier.invalid
UIApplication.shared.endBackgroundTask(self.backgroundTaskId!)
}
}
// SceneDelegate.swift
func sceneDidEnterBackground(_ scene: UIScene) {
// Extend the app's background execution time.
// Perform the task on a background queue.
DispatchQueue.global().async {
// Request the task assertion and save the ID.
self.backgroundTaskId = UIApplication.shared.beginBackgroundTask {
// End the task if time expires.
UIApplication.shared.endBackgroundTask(self.backgroundTaskId!)
self.backgroundTaskId = UIBackgroundTaskIdentifier.invalid
}
// Do your task here...
// ...
// End the task assertion.
self.backgroundTaskId = UIBackgroundTaskIdentifier.invalid
UIApplication.shared.endBackgroundTask(self.backgroundTaskId!)
}
}
LoomError
passed to PlayerEventHandler.didFail(_ : , error:)
was defined as below.
enum LoomError: Error {
struct PlaybackError: Error {
var code: String
}
// Please refer to the playback error document for more details.
case playback(PlaybackError)
case startIndexOutOfRange
case emptyVideoContents
case invalidWindowHierarchy
}
Control playback through LoomPlayer
.
player.play()
player.pause()
player.rewind()
player.forward()
player.previous()
player.next()
player.seek(to: seconds)
player.mute()
player.unmute()
// Show control panel including playback controls and progress bar.
// This function also enables automatically showing or hiding control panel when a tap event is detected.
player.showPlaybackControlsAndProgressBar()
// Hide control panel including playback controls and progress bar.
// This function also disables automatically showing or hiding control panel when a tap event is detected.
player.hidePlaybackControlsAndProgressBar()
// A Bool for if player is currently in fullscreen mode.
// If player is presented through `Loom.shared.presentPlayer()`, this value will always return `false`.
player.isFullscreen
// Enter fullscreen mode with autorotate enabled/disabled.
// This method will do nothing if it is called when there is an existing player in fullscreen mode.
player.enterFullscreen(autoRotate: autoRotateEnabled)
// Exit fullscreen mode.
player.exitFullscreen()
⚠️ Fullscreen mode is not supported when the player is presented modally.
To disable autorotation when player is in fullscreen, you need extra configuration in AppDelegate
. Use RotationHelper.lockedOrientationMask
to get player's desired locked orientation.
// AppDelegate.swift
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
// Return locked orientation mask if player's autorotate is disabled.
RotationHelper.lockedOrientationMask ?? .all
}
To enable background playback, make sure you turned on the Background Modes capability for your app and had "Audio, AirPlay, and Picture in Picture" option checked. For more details, please see Apple Developer Documentation | Enabling Background Audio.
// The default value is `false`.
player.isBackgroundPlaybackEnabled = true
// The duration the player should buffer media from the network ahead of the playhead to guard against playback disruption. The value is 60.0 by default.
//
// This property defines the preferred forward buffer duration in seconds. If set to 0, equal to or higher than 60, the player will choose an appropriate level of buffering for most use cases. Setting this property to a low value will increase the chance that playback will stall and re-buffer, while setting it to a high value will increase demand on system resources.
//
// - Note: AVPlayer may not fully respect the value. There might be slight deviation from the actual buffer duration.
player.preferredForwardBufferDuration = 10.0