---
version: "2026-09"
language: "en"
---
# SDK Release Summary

## September 2026

The table below shows the current status of NAGRAVISION SDK releases.  

|            **SDK**             |       **Platform/app**        | **Release** |  **Date**   |                                                              **New features**                                                               |                                                                            **Fixes**                                                                            |                                                                              **Release Notes**                                                                              |
|--------------------------------|-------------------------------|-------------|-------------|---------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **CONNECT Player**             | **Android**                   | 5.40.0      | 15 Dec 2025 | Custom content request headers                                                                                                              | None                                                                                                                                                            | [Android SDK 5.40.0 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-android-docs/5.40.x/Default/android-sdk-5-release-notes)                                 |
| **CONNECT Player**             | **Android**                   | 5.41.1      | 14 May 2026 | None                                                                                                                                        | Some STBs fail to create AudioTrack when the stream audio format changes.                                                                                       | [Android SDK 5.41.1 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-android-docs/5.41.x/Default/android-sdk-5-release-notes)                                 |
| **CONNECT Player**             | **Android**                   | 5.42.0      | 01 May 2026 | Key Per Track for Download to Go Common Media Client Data (CMCD) Decode-only flag for tunneling mode Updated Connect security level mapping | Text track characteristics misinterpreted as clean audio "enhances-speech-intelligibilty"                                                                       | [Android SDK 5.42.0 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-android-docs/5.42.x/Default/android-sdk-5-release-notes)                                 |
| **CONNECT Player**             | **Apple (FPS)**               | 5.14.0      | 16 Oct 2025 | iOS26/tvOS26 support Customer HTTP Header for streaming Build with Xcode 26                                                                 | None                                                                                                                                                            | [Apple (FPS) SDK 5.14.0 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-release-notes)                         |
| **CONNECT Player**             | **Apple (FPS)**               | 5.14.1      | 30 Apr 2026 | None                                                                                                                                        | Compatibility with latest Apple APIs                                                                                                                            | [Apple (FPS) SDK 5.14.1 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-release-notes)                         |
| **CONNECT Player**             | **Apple (FPS)**               | 5.14.2      | 01 Sep 2026 | None                                                                                                                                        | Fixed issue where downloads where audio tracks that were needed were not downloaded.                                                                            | [Apple (FPS) SDK 5.14.2 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-release-notes)                         |
| **CONNECT Player**             | **Browsers and Connected TV** | 5.26.0      | 16 Jan 2026 | Shaka Player engine updated to v4.16.13.                                                                                                    | Thumbnails not loaded occasionally. HbbTV Widevine encrypted stream playback.                                                                                   | [Browsers and Connected TV SDK 5.26.0 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-browsers/5.26.x/Default/browsers-and-connected-tv-sdk-5-release-notes) |
| **CONNECT Player**             | **Browsers and Connected TV** | 5.28.0      | 11 May 2026 | Support for LG webOS 2026 devices. Shaka Player engine updated to v4.16.29.                                                                 | None                                                                                                                                                            | [Browsers and Connected TV SDK 5.28.0 Release Notes](https://docs.nagra.com/connect-player-sdk-5-for-browsers/5.28.x/Default/browsers-and-connected-tv-sdk-5-release-notes) |
| **CONNECT Player**             | **React Native (Deprecated)** | 1.15.6      | 04 Sep 2024 | No new features.                                                                                                                            | mpd 400 error not reported via new onHttpError event                                                                                                            | [React Native SDK 1.15.6 Release Notes](https://docs.nagra.com/connect-player-react-native-sdk-docs/1.15.x/react-native-sdk-release-notes)                                  |
| **CONNECT Player**             | **TV Demo**                   | 1.6.5       | 08 Jan 2026 | None                                                                                                                                        | Incorrect configuration of `adaptiveBuferringGoal` feature of opy-sdk-js.                                                                                       | [TV Demo 1.6.5 Release Notes](https://docs.nagra.com/connect-player-tv-demo/1.6.x/tvdemo-release-notes)                                                                     |
| **CONNECT Player**             | **Visual Timeline**           | 1.2.0       | 15 Apr 2024 | Manifest exposure.                                                                                                                          | No bug fixes.                                                                                                                                                   | [Visual Timeline 1.2.0 Release Notes](https://docs.nagra.com/connect-player-visual-timeline/1.2.x/visual-timeline-release-notes)                                            |
| **DAS Client**                 | **Android**                   | 5.5.0       | 22 Oct 2024 | None                                                                                                                                        | No error definition in production build                                                                                                                         | [DAS Client for Android 5.5.0 Release Notes](https://docs.nagra.com/das-client-for-android-docs/5.5.x/Default/das-client-for-android-release-notes)                         |
| **DAS Client**                 | **Android**                   | 5.6.0       | 05 Mar 2026 | Exposing license expiring before expired                                                                                                    | None                                                                                                                                                            | [DAS Client for Android 5.6.0 Release Notes](https://docs.nagra.com/das-client-for-android-docs/5.6.x/Default/das-client-for-android-release-notes)                         |
| **DAS Client**                 | **Apple**                     | 5.5.0       | 22 Oct 2025 | tvOS Binary Libs support Toolchain update to Xcode 26                                                                                       | No bug fixes.                                                                                                                                                   | [DAS Client for Apple 5.5.0 Release Notes](https://docs.nagra.com/das-client-for-apple-docs/5.5.x/Default/das-client-for-apple-release-notes)                               |
| **DAS Client**                 | **Browsers and Connected TV** | 5.4.0       | 18 Dec 2025 | Refactor for complexity reduction and reliability.                                                                                          | Samsung Tizen device ID changing after reinstallation.                                                                                                          | [DAS Web Client 5.4.0 Release Notes](https://docs.nagra.com/das-web-client-for-browsers-and-connected-tv-docs/5.4.x/Default/das-web-client-release-notes)                   |
| **DAS Client**                 | **React Native**              | 1.1.0       | 24 Jan 2023 | Native Client update: * Android support for CONNECT PRM * Apple rebuilt for iOS/tvOS 16                                                     | No bug fixes.                                                                                                                                                   | [DAS React Native Client 1.1.0 Release Notes](https://docs.nagra.com/das-react-native-client-docs/1.1.x/das-react-native-client-release-notes)                              |
| **Insight Agent**              | **Android**                   | 1.3.3       | 16 Jul 2024 | No new features.                                                                                                                            | Invalid `timeToVideoStart` reported                                                                                                                             |                                                                                                                                                                             |
| **Insight Agent**              | **Apple**                     | 1.4.0       | 28 Nov 2024 | No new features.                                                                                                                            | New CI pipeline. No code changes.                                                                                                                               | [Insight Agent for Apple 1.4.0 Release Notes](https://docs.nagra.com/insight-agent-for-apple/1.3.x/downloads)                                                               |
| **Insight Agent**              | **Browsers**                  | 1.3.7       | 29 Mar 2023 | No new features.                                                                                                                            | No bug fixes.                                                                                                                                                   | [Insight Agent for Browsers 1.3.7 Release Notes](https://docs.nagra.com/insight-agent-for-browsers/1.0.x/downloads)                                                         |
| **Insight Agent**              | **React Native**              | 1.2.0       | 13 Sep 2023 | React Native Player SDK simulator support for iOS and tvOS.                                                                                 | Connected TV - Insight reported LG TV as NIKEM2. White screen seen when trying to load on Tizen 5.5. onStatisticsUpdate was unable to report adaptiveStreaming. | [Insight React Native 1.2.0 Release Notes](https://docs.nagra.com/insight-react-native-docs/1.2.x/insight-react-native-release-notes)                                       |
| **Insight Agent**              | **Linux**                     | 1.6.2       | 21 Jan 2026 | None                                                                                                                                        | Linux box reported timestamp from 500+ years in the future. Data timeToVideoStart invalid time and event timestamp                                              | [Insight Agent for Linux 1.6.2 Release Notes](https://docs.nagra.com/insight-agent-for-linux/1.6.x/insight-agent-for-linux-release-notes)                                   |
| **Media3 NAGRA PRM Extension** |                               | 1.0.0       | 11 Mar 2026 | Initial release                                                                                                                             | N/A                                                                                                                                                             | [Media3 NAGRA PRM Extension 1.0.0 Release Notes](https://docs.nagra.com/media3-nagra-prm-extension/1.0.x/media3-nagra-prm-extension-release-notes)                          |
| **Media3 NAGRA PRM Extension** |                               | 1.1.0       | 15 Jul 2026 | Support for: * VOD HLS with CPAK * Live HLS with CPAK * Local file HLS with CPAK                                                            | N/A                                                                                                                                                             | [Media3 NAGRA PRM Extension 1.1.0 Release Notes](https://docs.nagra.com/media3-nagra-prm-extension/1.1.x/media3-nagra-prm-extension-release-notes)                          |
| **OTV Analytics Agent**        | **Android**                   | 1.3.6       | 24 Jun 2025 | None                                                                                                                                        | Activity timestamps are not UTC but local time.                                                                                                                 | [OTV Analytics Agent for Android 1.3.6 Release Notes](https://docs.nagra.com/otv-analytics-agent-for-android/1.3.x/opentv-analytics-agent-for-android-release-notes)        |
| **OTV Analytics Agent**        | **Browsers**                  | 1.3.0       | 29 Jan 2025 | Trigger player metrics and error data only once.                                                                                            | N/A                                                                                                                                                             | [OTV Analytics Agent for Browsers 1.3.0 Release Notes](https://docs.nagra.com/otv-analytics-agent-for-browsers/1.3.x/release-notes)                                         |
| **OTV Analytics Agent**        | **Apple**                     | 1.0.0       | 16 Sep 2024 | First official release                                                                                                                      | N/A                                                                                                                                                             | [OTV Analytics Agent for Browsers 1.0.0 Release Notes](https://docs.nagra.com/otv-analytics-agent-for-apple/1.0.0/otv-analytics-agent-for-apple-release-notes)              |

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# CONNECT Player SDK 5 for Apple (FPS) Documentation

The CONNECT Player SDK for Apple (FPS) enables you to develop a secure player that can playback Apple FairPlay Streaming (FPS) encrypted content. It is available in two versions, iOS and tvOS, and supports HD and 4K playback. Media playback is enabled using the Swift class OTVAVPlayer, which inherits all properties and functions from AVPlayer, and extends AVPlayer to simplify FPS integration and support additional functionality.

## Supported versions and formats

Apple iOS and tvOS release 26 are supported by the SDK.  

| **Supported OS versions** | **Adaptive Streaming Format** | **DRM**  |   **Audio/Video Container**   | **Encryption Method**  |
|---------------------------|-------------------------------|----------|-------------------------------|------------------------|
| iOS releases 16-26        | HLS                           | FairPlay | ISOBMFF/F-MP4 (CMAF) MPEG2-TS | AES-128 CBC SAMPLE-AES |
| tvOS releases 16-26       | HLS                           | FairPlay | ISOBMFF/F-MP4 (CMAF) MPEG2-TS | AES-128 CBC SAMPLE-AES |

## iOS SDK file contents

The iOS SDK typically contains the following files:

* **opy-sdk-ios-fps-\<version\>-integration.zip**

  This contains the framework file used for integration activities and is necessary for producing debug logs; see the [Apple (FPS) SDK Integration Guide: Creating the player](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/creating-the-player.md).

* **opy-sdk-ios-fps-\<version\>-production.zip**

  This contains the framework file that replaces the integration version when the application is ready to be deployed; see the [Apple (FPS) SDK Integration Guide: Building the production version](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/building-the-production-version.md).

* **opy-sdk-ios-fps-\<version\>-xcframework-integration.zip**

  This contains the SDK xcframework file used for integration activities and is necessary for producing debug logs; see the [Apple (FPS) SDK Integration Guide: Creating the player](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/creating-the-player.md).

* **opy-sdk-ios-fps-\<version\>-xcframework-production.zip**

  This contains the SDK xcframework file that replaces the integration version when the application is ready to be deployed; see the [Apple (FPS) SDK Integration Guide: Building the production version](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/building-the-production-version.md).

* **opy-sdk-ios-fps-\<version\>-docs.zip**

  This contains the API files; see [Apple (FPS) SDK APIs](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-apis.md).

* **opy-sdk-ios-fps-\<version\>-insight-agent-wrapper.zip**

  This contains the additional libraries for the [Insight agent wrapper](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/insight-analytics.md).

* **opy-sdk-ios-fps-\<version\>-example-code.zip**

  This contains code examples to demonstrate the features of the CONNECT player; see the [Apple (FPS) Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

* **opy-sdk-ios-fps-\<version\>-quickmark.zip**

  This contains the additional libraries for the [QuickMark forensic watermarking](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/quickmark-forensic-watermarking.md) feature.

* **opy-sdk-ios-fps-\<version\>-quickmark-docs.zip**

  This contains the API files for the [QuickMark forensic watermarking](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/quickmark-forensic-watermarking.md) feature; see [Apple (FPS) SDK APIs](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-apis.md).

* **opy-sdk-ios-fps-\<version\>-insight-agent.zip**

  This contains the additional libraries for the [Insight agent](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/insight-analytics.md).

* **opy-sdk-ios-fps-\<version\>-insight-agent-wrapper-docs.zip**

  This contains the API files for the [Insight analytics](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/insight-analytics.md) feature; see [Apple (FPS) SDK APIs](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-apis.md).

## tvOS SDK file contents

The tvOS SDK contains the following files:

* **opy-sdk-tvos-fps-\<version\>-integration.zip**

  This contains the framework file used for integration activities; see the [Apple (FPS) SDK Integration Guide: Creating the player](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/creating-the-player.md).

* **opy-sdk-tvos-fps-\<version\>-production.zip**

  This contains the framework file that replaces the integration version when the application is ready to be deployed; see the [Apple (FPS) SDK Integration Guide: Building the production version](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/building-the-production-version.md).

* **opy-sdk-tvos-fps-\<version\>-docs.zip**

  This contains the API files; see [Apple (FPS) SDK APIs](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-apis.md).

* **opy-sdk-tvos-fps-\<version\>-insight-agent-wrapper.zip**

  This contains the additional libraries for the [Insight agent wrapper](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/insight-analytics.md).

The tvOS SDK supports all of the same features as the iOS SDK except for Offline playback and Adverts with Google IMA.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Adding player features

Having created your app with basic (clear) playback, you can now add the required features to it; see [Apple (FPS) SDK 5 Player Features](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-player-features.md).

**Next step:** Finally, [build the production version](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/building-the-production-version.md).

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Adverts with Google IMA

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

You can extend the CONNECT Player SDK for FPS to support the insertion of linear adverts using the third-party component `IMAWrapper `for the Google Interactive Media Ads (IMA) framework.  
Adverts are only supported on iOS (Google do not provide an IMA framework for tvOS).

## Prerequisites

An advert server already configured, and knowledge of the advert tag URLs used to access it.

An understanding of the CocoaPods technology, see [Using CocoaPods](http://guides.cocoapods.org/using/).

## Procedure

The process is split into two stages:

* Installing IMA CocoaPods

* Enabling playback of linear adverts

## Installing the IMA CocoaPods pod

To carry out a pod integration from scratch, begin at step 1. If you are using the dynamic-ads-ima example application, start at step 3 as the pod file is already present.

1. Install cocoapods by running the following command.

   Bash

       sudo gem install cocoapods

2. Create your app's `podfile` in the project root folder by running `pod init` and add the IMA pod dependency, which should look something like this.

   Bash

       target '<target_name>' do
           use_frameworks!
           source 'https://github.com/CocoaPods/Specs.git'
           platform :ios, '9.0'
           pod 'GoogleAds-IMA-iOS-SDK', '~> 3.9'
           end

3. To apply this to your project, execute the following command in the project's root folder (where the Xcode project file is located).

   Bash

       pod install  --repo-update

   The `--repo-update` switch is only required if you need the pod details to be reflected into the corresponding project workspace files, for example, the first time it is run.

There will now be a `.xcworkspace` configured with the IMA Pod linking capability; use this instead of the`.xcodeproj`.

## Linear adverts

To enable playback of linear adverts, see [Playback of linear adverts](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/playback-of-linear-adverts.md).

## Companion adverts

To add companion ads, see the [Google Developer's Guide](https://developers.google.com/interactive-media-ads/docs/sdks/ios/client-side/companions).

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Apple (FPS) SDK 5 Example Code Quick Start

This guide enables you to demonstrate CONNECT Player features and view the example code.  
Example code is provided for iOS only.

## Prerequisites

Make sure you have the latest version of Xcode installed.

[Download the iOS SDK](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/downloading-the-sdk.md), which supplies all the files needed to launch the player and start streaming content.

* **opy-sdk-ios-fps-5.14.x-example-code.zip**

  This file contains application projects that demonstrate how to use simple and advanced features, such as playing clear and encrypted streams.

* **opy-sdk-ios-fps-5.14.x-integration.zip**

  This contains the SDK framework files.

* **opy-sdk-ios-fps-5.14.x-xcframework-integration.zip**

  This contains the SDK xcframework file.

You also need a device running iOS 12 or above.

## Procedure

1. Save the SDK pack to your development machine and unzip it. The extracted package contains a zipped set of example code; extract the contents of the **example-code.zip** file to a suitable location.

2. Copy the SDK framework \& xcframework folder into the **example-code** folder.

3. Start Xcode, and then select the required project from the extracted package; see the example projects below.

4. Connect an Apple device to your machine, and build and run the application.

Click here to see the example projects available.  

|            **Playback**             |                                                                                                         **basic-playback**                                                                                                         | Demonstrates playback of a clear HLS stream with no additional functionality. |
|            **Playback**             |
|            **Playback**             |
|            **Playback**             |
|            **Playback**             |
|            **Playback**             |
|            **Playback**             |
|            **Playback**             |
|            **Playback**             |
|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
| **basic-playback-xcframework**      | Demonstrates playback of a clear HLS stream with no additional functionality using SDK xcframework.                                                                                                                                |
| **low-latency**                     | Playback of [low-latency](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/playback-of-clear-content.md) HLS streams.                                                                                                       |
| **container-view**                  | Manages the [view hierarchy](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/container-view.md) for standard playback.                                                                                                     |
| **resolution-capping**              | Playback of clear HLS streams with [resolution capping](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/resolution-capping.md).                                                                                            |
| **objective-c support**             | Code development using Objective-C.                                                                                                                                                                                                |
| **thumbnail-seeking-webvtt**        | Playback of clear HLS streams with [webvtt](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/enabling-webvtt-thumbnail-previews.md) thumbnail previews.                                                                     |
| **thumbnail-seeking-iframe**        | Playback of clear HLS streams with [iFrame](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/enabling-i-frame-thumbnail-previews.md) thumbnail previews.                                                                    |
| **track-selection**                 | Playback of clear HLS streams with [multiple audio tracks and subtitles](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/track-selection.md).                                                                              |
| **encrypted-playback-advanced**     | Integration using low-level license delegate to support [Playback of FPS encrypted content](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/playback-of-fps-encrypted-content.md).                                         |
| **encrypted-playback-ssm**          | Playback of FPS encrypted content with [Secure Session Management](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/secure-session-management.md) (SSM).                                                                    |
| **encrypted-playback-advanced-ssm** | Integration using low-level license delegate to support playback of FPS encrypted content with SSM.                                                                                                                                |
| **customer-ssm**                    | Provides a solution for [customers to handle SSM](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/customer-ssm.md).                                                                                                        |
| **encrypted-playback-binary**       | Playback of FPS encrypted content but the network request for decryption returns binary data.                                                                                                                                      |
| **offline-encrypted**               | Downloads an encrypted HLS stream while [requesting a licence](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/retrieving-the-licence.md) and then plays offline content.                                                  |
| **offline-license-predelivered**    | [Prefetches the licence](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/retrieving-the-licence.md) before downloading an encrypted HLS stream and playing offline content.                                                |
| **offline-license-renewal**         | Downloads an encrypted HLS stream, obtains the licence, plays the offline content and [Renews the licence](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/renewing-a-licence.md) when a licence expiry error is received. |
| **minimumbitrate-for-offline**      | Select the [minimum bitrate](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/starting-the-download.md) to download for offline playback.                                                                                   |
| **minimumresolution-for-offline**   | Select the [minimum resolution](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/starting-the-download.md) to download for offline playback. (Only available for iOS 14 and above).                                         |
| **offline-prepare-download**        | Select the [download stream](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/selecting-the-download-stream.md) before downloading starts.                                                                                  |
| **drm-token-passing**               | Parse the SSP DRM stream token to get the licence duration and set it to offline content.                                                                                                                                          |
| **quickmark-push-mode**             | Demonstrates [QuickMark forensic watermarking](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/quickmark-forensic-watermarking.md) in push mode.                                                                           |
| **serverside-ad-insertion**         | Demonstrates use of adverts within streams with [Server-Side Ad Insertion](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/server-side-ad-insertion.md).                                                                   |
| **yospace-controlbar**              | Demonstrates the integration of the [Yospace](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/yospace.md) user interface handling of streams with Server-Side Ad Insertion.                                                |
| **network-statistics**              | Demonstrates use of statistics API to get [network statistics](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/network-statistics.md) information.                                                                         |
| **insight-agent-wrapper**           | Sends playback metrics and statistics to an [Insight analytics](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/insight-analytics.md) server.                                                                              |
| **Log-Production**                  | Enables [logging in production builds](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/logging-in-production-builds.md).                                                                                                   |
| **smart-lib**                       | Demonstrates use of [Broadpeak SmartBeam](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/broadpeak-smartlib-support.md).                                                                                                  |
| **output-device-monitor**           | Demonstrates use of[OTVOutputDeviceMonitor](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/external-device-detection.md) and the monitoring of external connections and disconnections.                                   |

Features in the above example code (except **objective-c support** and**yospace-controlbar** ) are also integrated as part of **UnifiedExampleCode** using **SwiftUI**which can run on iOS and tvOS device running version 16 and above.  
For full details on integrating the SDK with your application, see the [Apple (FPS) SDK 5 Integration Guide](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-integration-guide.md). See also the API reference guide provided in the iOS SDK pack.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Apple (FPS) SDK 5 Integration Guide

The following procedures describe creating a new CONNECT Player application in Apple (FPS) iOS or tvOS and playing a specified clear video stream.  
For upgrading the CONNECT Player, please see the [Apple SDK 3.x to 5.x Migration Guide](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-sdk-3-x-to-5-x-migration-guide.md).

To test the features and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

## Prerequisites

### Tools

The following development tools are required:

* The latest version of Xcode.

* A handheld device supporting iOS 11 or above, or an Apple TV supporting tvOS 12 and above.

Simulators can be used during development; however simulator support must be removed from the version for submission to the Apple store; see [Removing simulator support](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/building-the-production-version.md).

### Product release files

* **opy-sdk-ios-fps-5.12.x-integration.zip** / **opy-sdk-tvos-fps-5.12.x-integration.zip**

  These contain the SDK framework files used for iOS or tvOS integration activities.

* **opy-sdk-ios-fps-5.12.x-production.zip** / **opy-sdk-tvos-fps-5.12.x-production.zip**

  These contain the SDK framework files that replace the integration versions when the finished application is ready to be deployed.

* **opy-sdk-ios-fps-5.12.x-xcframework-integration.zip**

  This contains the SDK xcframework integration file which includes libraries for iOS, tvOS, iOS simulator and tvOS simulator.

* **opy-sdk-ios-fps-5.12.x-xcframework-production.zip**

  This contains the SDK xcframework file that replaces the integration version when the finished application is ready to be deployed.

## Integration process

NAGRA recommends you perform integration of the CONNECT Player SDK in the following stages:

* In conjunction with the [Apple (FPS) Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide, use the code examples to explore and test the features of the CONNECT player.

* Using this guide, create your own basic player. For details see:

  * [Creating the player](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/creating-the-player.md)

  * [Running the application](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/running-the-application.md)

* Develop your code to add encrypted playback and other features to the player.

  * [Adding player features](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/adding-player-features.md)

* When development is complete, replace the integration file with the production version. Carry out final testing and submit to the Apple Store.

  * [Building the production version](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/building-the-production-version.md)

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Apple (FPS) SDK 5 Player Features

This section provides technical descriptions of the main CONNECT Player features and includes example code snippets to show how to implement them.  

|         **Playback**         | [Playback of clear content](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/playback-of-clear-content.md) [Container view](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/container-view.md) [Multi-instance](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/multi-instance.md) [Offline playback](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/offline-playback.md) (iOS only) [Resolution capping](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/resolution-capping.md) [Thumbnail previews](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/thumbnail-previews.md) |
|    **Encrypted playback**    |                                                                                                                                                                                                                                              [Playback of FPS encrypted content](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/playback-of-fps-encrypted-content.md)                                                                                                                                                                                                                                               |
|     **Content security**     |                                                                                                                                           [QuickMark forensic watermarking](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/quickmark-forensic-watermarking.md) [Secure Session Management](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/secure-session-management.md) [Customer SSM](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/customer-ssm.md)                                                                                                                                            |
|         **Adverts**          |                                                                                                                                                    [Adverts with Google IMA](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/adverts-with-google-ima.md) (iOS only) [Server-side ad insertion](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/server-side-ad-insertion.md) [Yospace](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/yospace.md)                                                                                                                                                    |
| **Statistics and analytics** |                                               [Logging in production builds](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/logging-in-production-builds.md) [Event timeline](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/event-timeline.md) [Insight analytics](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/insight-analytics.md) [Network statistics](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/network-statistics.md) [Playback statistics](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/playback-statistics.md)                                                |
|     **Language support**     |                                                                                                                                                                                                                                                                [Track selection](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/track-selection.md)                                                                                                                                                                                                                                                                 |
|      **Other features**      |                                                                                                                                                                                            [External device detection](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/external-device-detection.md) [Broadpeak SmartLib support](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/broadpeak-smartlib-support.md)                                                                                                                                                                                             |
|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Apple (FPS) SDK 5 Release Notes

The current version of the Apple (FPS) SDK 5 is 5.14.1.  
For the full version history see <https://docs.nagra.vision/sdk-release-summary/latest>  

|               **Release**               |                **Purpose**                 |                              **New features**                               |                                      **Fixes**                                       |                                                                                                            **Known issues**                                                                                                            |
|-----------------------------------------|--------------------------------------------|-----------------------------------------------------------------------------|--------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **5.14.2** **Release date:** 2026-09-01 | This is a maintenance release.             | None                                                                        | Fixed issue where downloads where audio tracks that were needed were not downloaded. | get error info from the OTVLicenseDownloadEnded notification. The same iframe thumbnail is shown anywhere on the seek bar. A random HTTP 500 error is returned when trying to download multiple downloads at once in quick succession. |
| **5.14.1** **Release date:** 2026-04-30 | This is a maintenance release.             | None                                                                        | Compatibility with latest Apple APIs.                                                | get error info from the OTVLicenseDownloadEnded notification. The same iframe thumbnail is shown anywhere on the seek bar. A random HTTP 500 error is returned when trying to download multiple downloads at once in quick succession. |
| **5.14.0** **Release date:** 2025-10-16 | This is a feature and maintenance release. | iOS26/tvOS26 support Customer HTTP Header for streaming Build with Xcode 26 | None                                                                                 | get error info from the OTVLicenseDownloadEnded notification. The same iframe thumbnail is shown anywhere on the seek bar. A random HTTP 500 error is returned when trying to download multiple downloads at once in quick succession. |

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Apple (FPS) SDK APIs

Online API documentation for CONNECT Player SDK 5 for Apple (FPS) release 5.14.1:

* [Swift](https://docs.nagra.com/public/apidocs/player/ios-fps/opy-sdk-ios-fps-5.14.2.1784719599-docs/docs/swift/index.html)

* [Objective-C](https://docs.nagra.com/public/apidocs/player/ios-fps/opy-sdk-ios-fps-5.14.2.1784719599-docs/docs/objC/index.html)

* [QuickMark forensic watermarking](https://docs.nagra.com/public/apidocs/player/ios-fps/opy-sdk-ios-fps-5.14.2.1784719599-quickmark-docs/docs/index.html)

* [Insight agent wrapper](https://docs.nagra.com/public/apidocs/player/ios-fps/opy-sdk-ios-fps-5.14.2.1784719599-insight-agent-wrapper-docs/docs/index.html)

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Apple SDK 3.x to 5.x Migration Guide

The CONNECT Player iOS/tvOS SDK 5.x is NAGRA's latest iOS and tvOS player with multi-DRM. It supports Apple's FairPlay Streaming (FPS) and NAGRA's proprietary software PRM . This guide describes how SDK 3.x users can migrate to SDK 5.x.

## Integration differences

### Server-side DRM

The DRM server has to enable PRM service and disable the key-ladder configurations.

### Other differences

| **Differences**  |    **SDK 3.x**    |                                     **SDK 5.x**                                     |
|------------------|-------------------|-------------------------------------------------------------------------------------|
| DRM              | PRM               | FPS and PRM (iOS only)                                                              |
| SDK library type | Static lib in zip | Dynamic in frameworks                                                               |
| SDK name         | nmp-sdk           | OTVSDKFPS.framework OPYSDKFPSTv.framework OTVSDKPRM.framework OTVSDKFPS.xcframework |
| Supported OS     | iOS 11+           | iOS 11+ tvOS 11+ (tvOS has FPS support only)                                        |

## Offline playback (Download to Go)

SDK 5.x supports background downloading for offline playback.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Broadpeak SmartLib support

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The **smartbeam example code** shows how an application can playback streams in conjunction with Broadpeak SmartBeam set-top box. This example depends on the SmartLib library, which detects and controls SmartBeam set-top boxes connected to the same LAN as the Android device.

## Building and running the Smart-Lib example code

To build Smart lib example code:

1. Install Cocoapods. The easiest way to do this is **$ sudo gem install cocoapods** ; see the Cocoapods installation guide at [https://cocoapods.org](https://cocoapods.org/).

2. Change the username and password inside the podfile to the values associated with your smart lib account. Source <https://username:password@delivery-platform.broadpeak.tv/ios/broadpeak/specs.git>.

3. Run the **pod install** command from the working directory. This will fetch the smart lib dependency and make an .xcworkspace which you will use to build and install the example code.

4. Build and install the application from the .xcworkspace.

You must have your own Smart Lib account to fetch the dependency from Cocoapods.

You may need to update the streams inside the example code by changing the URLs in the ViewController.m. The variables to change are stream1 and stream2.

To run `OTVAVPlayer` with a smartbeam gateway box, you must use an external library that creates a SmartLib client for the current streaming session and attaches it to the player. The following values are required to initiate the SmartLib client:

* analyticsAddress: The URL for analytics

* nanCDNHost: The address inside the device's home network where the nanoCDN is embedded, or discover if the discovery is enabled on the nanoCDN.

* broadpeakDomainNames: The domain name list used to identify URL(s) using the Broadpeak product

A specific value `"*"` is used to declare that all the given URLs are using the broadpeak product.

An empty value `""` is used to declare that no given URLs are using the Broadpeak value.

Discovery will take place if the Android device is in the same sub-network as the SmartBeam box.

## Prerequisites

A SmartBeam box and the library mentioned in the example code.

## Example code

The following example code is used to configure SmartLib.
Click here to see the example code.  

### **Smart lib client example code**

     /**
      * Init core, async nanoCDN resolution, register to app and system events
      *
      * Note: To be called once when the app starts
       
      * @param analyticsAddress Address of the analytics server (i.e. "http://server-host:8080")
      * @param nanoCDNHost A nanoCDN host configuration. null/empty string "" or
      *                    "discover" to enable auto-discovery or
      *                    a nanoCDN host list (i.e "192.168.1.1,192.168.1.10")
      * @param broadpeakDomainNames The domain name list used to identify Broadpeak sessions (i.e "cdn.broadpeak.com,cdn2.broadpeak.com")
      *                             "*" specific value is used to declare that all sessions are using a Broadpeak CDN
      *                             null/empty string "" is used to declare that all given url are not hosted on a Broadpeak CDN
    - (instancetype)initWith:(NSString *)analyticsAddress
         nanoCDNHost:(NSString *)nanoCDNHost
    broadpeakDomainNames:(NSString *)broadpeakDomainNames
    {
      self = [super init];
      if (self) {
        [SmartLib registerNanoCDNReceiver:self];
        [SmartLib initSmartLib:analyticsAddress nanoCDNHost:nanoCDNHost broadpeakDomainNames:broadpeakDomainNames];
      }
      return self;
    }

     // getURL (session start, default method)
    - (NSString *)getURL:(NSString *)requestedURL {
      if (playerAttached) {
         return [SmartLib getURL:requestedURL];
      }
      return @"";
    }

    //Attach a player to SmartLib to handle analytics

    - (void)attachPlayer:(NSObject *)player {
      [SmartLib attachPlayer:player];
      playerAttached = true;
    }

Once the nanoCDN is registered, the SmartLib and the `OTVAVPlayer` have been created, pass the stream URI to the client:
Click here to see the example code.  
**Smart lib client example code**

     
    smartLibClient = [[SmartLibClient alloc]initWith:@"" nanoCDNHost:@"discover" broadpeakDomainNames:@""];
      
      otvLicenseDelegate = [[OTVDefaultLicenseDelegate alloc]initWithCertificateURL:certificateURL licenseURL:licenseURL];
      drmManager = [OTVDRMManager shared];
      [drmManager setLicenseDelegate:otvLicenseDelegate];
      [otvLicenseDelegate setHTTPHeaderWithParameters:@{
          @"nv-authorizations" : sspToken
      }];
      
      otvPlayer = [[OTVAVPlayer alloc]initWithPlayerItem:nil];
      
      //Attach a player to SmartLib to handle analytics
      [smartLibClient attachPlayer:otvPlayer];
      
     // getURL (session start, default method)
      NSURL* url = [NSURL URLWithString:[smartLibClient getURL:assetURL.absoluteString]];
      if (url != nil) {
        otvPlayerItem = [[OTVAVPlayerItem alloc]initWithURL:url];
         [otvPlayer replaceCurrentItemWithPlayerItem:otvPlayerItem];
         [_playerView setPlayer:otvPlayer];
        
         [otvPlayer play];
      } else
      {
        NSLog(@"Smart lib returned a nil URL, please check configuration options and or stream variables");
      }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Building the production version

Upload to the AppStore will be blocked if your application's archive contains the SDK framework with unsupported simulator architectures and will need to be stripped out before submission; see [Excluding simulator support](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/simulator-support.md)

When code development is complete, create the production version for final testing and submission to the Apple Store.

1. Unzip the relevant production file and extract the framework file:

   * **opy-sdk-ios-fps-5.12.x-production.zip** / **OPYSDKFPS.framework**

   * **opy-sdk-tvos-fps-5.12.x-production.zip** / **OPYSDKFPSTv.framework**

   * **opy-sdk-ios-fps-5.12.x-xcframework-production.zip** / **OPYSDKFPS.xcframework**

2. In Xcode, replace the existing (integration) framework file with the production version above.

3. Build and run the application as before. The NAGRA logo is not present during playback.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Container view

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The CONNECT Player SDK for FPS provides `OTVContainerView`, which manages the necessary view hierarchy for a standard playback scenario.

After the setup for basic playback, you can swap out the `PlayerView` and `AVPlayerLayer` references for an `OTVContainerView`. To do this, change the class type of your `playerView` `IBOutlet` to `OTVContainerView` (and also the class type of that view in your storyboard). You can now remove the `PlayerView` class from your code.

## Applying a logo to a corner of the stream

`OTVContainerView` has a `customOverlayView` property, which is a `UIView` to which you can add any additional views you want to appear on top of the stream and subtitles but below the optional `OTVWatermark`. For example, you may want to add a logo or player controls such as a seek bar and track selection menus.

Views can be added directly to the `customOverlayView` with auto layout constraints, auto-resizing masks or laid out manually. Alternatively, views can be added to `OTVContainerView` in storyboard or interface builder and will automatically move to the `customOverlayView` at runtime.

The example below shows how to add a view to the `customOverlayView` and keep it aligned to the bottom right corner of the video. You can add this code to your `viewDidLoad` method.

        // Create a view and add it to the customOverlayView
        let label = UILabel()
        label.text = "This is a custom view added in code"
        label.textColor = .white
        label.textAlignment = .center
        label.numberOfLines = 0
        label.font = UIFont.boldSystemFont(ofSize: 26)
        label.backgroundColor = UIColor.black.withAlphaComponent(0.3)
        label.frame = CGRect(x: 0, y: 0, width: 200, height: 93.5)
        containerView.customOverlayView.addSubview(label)
      
        // Listen out for the AVPlayerLayer's videoRect changing.
        playerLayerObserver = containerView.playerLayer.observe(\AVPlayerLayer.videoRect) { playerLayer, _ in
          DispatchQueue.main.async {
            // Update our view to be in the bottom right of the video
            // Dispatched to the main thread as this is called on a background thread
            // and UI-related code needs to be on the main thread
            var frame = label.frame
            frame.origin.x = (playerLayer.videoRect.origin.x + playerLayer.videoRect.size.width) - frame.size.width
            frame.origin.y = (playerLayer.videoRect.origin.y + playerLayer.videoRect.size.height) - frame.size.height
            label.frame = frame
          }
        }

## Non-native subtitle formats

If you have a stream with ID3 SMPTE-TT/PNG subtitles, they will be rendered by the `OTVContainerView`, once selected, without any additional setup required. SRT subtitles will similarly be rendered if provided to the player via the `addSubtitleWithUrl(subtitleURL: mimeType: language:)` function.

## OTVWatermark

If you are using Nexguard QuickMark ([forensic watermarking](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/quickmark-forensic-watermarking.md)) to secure your streams, you can use the `bindWatermark` function of `OTVContainerView` to insert an `OTVWatermark` in the correct place of the view hierarchy.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Creating the player

Use the following procedure to create a new Apple iOS or tvOS project to build the basic player for app development using Swift.  
Alternative example code is provided for Objective-C development (not described here).

1. Unzip the required integration file and copy the framework file to an appropriate location (for example, the project root).

   * **opy-sdk-ios-fps-5.12.x-integration.zip** / **OPYSDKFPS.framework**

   * **opy-sdk-tvos-fps-5.12.x-integration.zip** / **OPYSDKFPSTv.framework**

   * **opy-sdk-ios-fps-5.12.x-xcframework-integration.zip** / **OPYSDKFPS.xcframework**

2. In Xcode, create a new **Single View** project for your iOS/tvOS platform.

   * In project options, select **Swift** as the language.

   * On the **General** tab, add the framework to **Frameworks, Libraries, and Embedded Content** , ensuring **Embed \& Sign** is selected.

   If you are using Xcode 12.3, a **Build Settings** error requires the re-addition of **VALIDATE_WORKSPACE** to the **Build Options** section under **Build Settings** . This must be added for both **Debug** and **Release** (set to YES for Release and NO for Debug).

   Also, if your project already has the **VALIDATE_WORKSPACE** setting, it may need to be changed, the target rebuilt, then changed back for the setting to be recognised properly.

   ![validate_workspace_light.png](https://docs.nagra.vision/__attachments/a_67983fdb7861c711bd8c14d749cda5bd2646648e045c73d41299c0a89ccb3f69/validate_workspace_light.png?cb=43c62ce3f6bc050c6912beb5896fc78f)

   This must be done for both **Debug** and **Release**.
3. To connect to non-HTTPS URLs, add Application Transport Security (ATS) settings to info.plist. Open the **info.plist** file and add an App Transport Security entry for your content (replacing www.example.com with the actual URL):

   XML

           <key>NSAppTransportSecurity</key>
             <dict>
             <key>NSExceptionDomains</key>
                 <dict>
                 <key>www.example.com</key>
                   <dict>
                   <key>NSIncludesSubdomains</key>
                   <true/>
                   <key>NSExceptionAllowsInsecureHTTPLoads</key>
                   <true/>
                 </dict>
               </dict>
             </dict>

4. Add the following import in `ViewController.swift`.

       import OPYSDKFPS // or OPYSDKFPSTv for tvOS

   Below the imports, add the code for the `PlayerView` class:

           class PlayerView: UIView {

               var player: AVPlayer? {
                   get { return playerLayer.player }
                   set { playerLayer.player = newValue }
               }

               var playerLayer: AVPlayerLayer {
                   return layer as! AVPlayerLayer
               }

               override class var layerClass: AnyClass {
                   return AVPlayerLayer.self
               }
           }

5. Add members for the player and player view below the `ViewController`class definition.

           let otvPlayer: OTVAVPlayer
           @IBOutlet weak var playerView: PlayerView!

   Add the content URL:

           let assetURL = URL(string:
           "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_clear/index.m3u8")!

6. Instantiate an `OTVAVPlayer`

   Ensure SDK has been loaded before constructing the player.

   Initialise the player in the `init` method by passing in the asset url:

           required init?(coder aDecoder: NSCoder) {
               OTVSDK.load()
               otvPlayer = OTVAVPlayer(url: assetURL)
               super.init(coder: aDecoder)
           }

   The `OTVAVPlaye`r takes either an `OTVAVPlayerItem` object or the URL of a content stream.
7. Inside the `viewDidAppear` method, assign our player to the `playerView` and start playback:

       override func viewDidAppear(_ animated: Bool) {
               super.viewDidAppear(animated)
               playerView.player = otvPlayer
               otvPlayer.play()
           }

   Your `ViewController` class should look like the following:

       class ViewController: UIViewController {

               let otvPlayer: OTVAVPlayer
               @IBOutlet weak var playerView: PlayerView!

               let assetURL = URL(string: "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_clear/index.m3u8")!

               required init?(coder aDecoder: NSCoder) {
                   OTVSDK.load()
                   otvPlayer = OTVAVPlayer(url: assetURL)
                   super.init(coder: aDecoder)
               }

               override func viewDidAppear(_ animated: Bool) {
                   super.viewDidAppear(animated)
                   playerView.player = otvPlayer
                   otvPlayer.play()
               }
           }

8. Link the view to the player:

   Open **Main.storyboard** and open the assistant editor. Drag from the small blue circle next to the `IBOutlet` that you added in `ViewController.swift` to the `ViewController` view:

![IBOutlet_1.png](https://docs.nagra.vision/__attachments/a_e70dd9a1bc42bc4516d1229111b388325304dca50962e04b194637c7e3009286/IBOutlet_1.png?cb=c23de7d31f266d376441ef31ec542d31)

**Next step:** [Run the app](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/running-the-application.md) in clear playback mode.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Customer HTTP Header

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The CONNECT Player SDK supports customer HTTP header for the streaming. It's utilized by the Apple's AVURLAsset initialization options: [https://developer.apple.com/documentation/avfoundation/avurlasset-initialization-options​](#)

The client application handles the initialization of the OTVAVURLAsset and put the corresponding options.

## Configuring the player for customer HTTP headers

Configuration of the player is described in the [Apple (FPS) SDK 5 Integration Guide](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-integration-guide.md).

For the options of customer HTTP headers, configure the OTVAVURLAsset as follows:

    // Initialize the AVURLAsset with the options
        // Select which option needed for the HTTP request
        let asset = OTVAVURLAsset(url: assetURL, options: assetOptions)
        
        // Create an AVPlayerItem for playback
        let playerItem = OTVAVPlayerItem(asset: asset)
        
        // initialise the player by passing in the playerItem.
        otvPlayer = OTVAVPlayer(playerItem: (playerItem))

You need to decide which option/options to use. Refer the Apple's documentation above for public options.

Connect Player verified the following options:

* **AVURLAssetHTTPCookiesKey - \> add cookie in http header**​

* **AVURLAssetHTTPUserAgentKey(iOS 16+) -\> overwrite user agent in http header**​

* **AVURLAssetHTTPHeaderFieldsKey** **-\> Add customer header, not public API, may be deprecated, still usable, risk of publish to App store**​

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Customer SSM

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

In nagra-ssp DRM mode, the CONNECT Player SDK supports [Secure Session Management](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/secure-session-management.md) using the standard SSP API to perform session setup and management and licence acquisition and renewal as necessary. These standard interfaces are not possible in some system architectures, for example, where an API gateway is used. The **customer-ssm** mode facilitates secure session management in such systems.

The client application handles session management, licence acquisition, and renewal when using the SDK in **customer-ssm** mode. The SDK prompts the client application when these operations are required, using a pair of callbacks provided in the license delegate.

## Configuring the player for customer SSM

Configuration of the player is described in the [Apple (FPS) SDK 5 Integration Guide](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-integration-guide.md).

For SSP with **customer-ssm**, configure the DRM as follows:

    let fairplayCertificate = getFairplayCertificate(certificateURL: fairplayCertificateURL) 
    let customerSSMDelegate = OTVCustomerSSMDelegate(certificate: fairplayCertificate)

    let customerCallback = CustomerCallback(licenseURL: licenseURL, ssmServerURL: ssmServerURL) 
    customerSSMDelegate.setCallback(customerCallback)

    OTVDRMManager.shared.setLicenseDelegate(customerSSMDelegate)

You need to implement the `getFairplayCertificate` function to return the certificate as `Data` type. Implement `CustomerCallback` under the protocol `OTVCustomerCalback`.

### OTVCustomerCallback protocol

    @objc public protocol OTVCustomerSSMCallback: NSObjectProtocol {

      /**
       Returns the licence containing Content Key Context (CKC) message, and heartbeat period.
       
       - Parameter keySystem: String corresponding to the desired key system (Fairplay for iOS)
       - Parameter payload: token for licence retrieval
       - Parameter licenseType: "license-request" (inital) or "license-renew" (subsequent periodic renewal)
       - Returns: tuple containing the licence data and the heartbeat period.
       */

      func license(keySystem: String, payload: Data, licenseType: OTVLicenseRequestType) -> OTVSSMLicenseResponse
      
      /**
       Calls the application supplied heartbeat function
       */
      func heartbeat()
    }

### License callback

The SDK will call the `license()` callback when a licence is first required, and periodically when it is required to be renewed. It will be necessary to perform a secure session setup before acquiring the licence for the first time.

    open func license(keySystem: String, payload: Data, licenseType: OTVLicenseRequestType) -> OTVSSMLicenseResponse 

### License parameters

* `keySystem` will be identified using the DASH ContentProtection Scheme identifier as shown in the table below.

* `payload` as passed from the `ckcMessage()` as SPC data and will be required for the licence acquisition.

* `licenseType` indicates whether this is the first request for a licence or whether the request is to renew an existing licence. Set using the OTVLicenseRequestType enumeration

### OTVLicenseRequestType eum

    ///License request type

    @objc(OTVLicenseRequestType)
    public enum OTVLicenseRequestType: Int {
      case request
      case renewal
    }

| **Key System** |            **Identifier**            |
|----------------|--------------------------------------|
| FairPlay       | 94CE86FB-07FF-4F43-ADB8-93D2FA968CA2 |

### License return value

The `license` callback will return an instance of `OTVSSMLicenseResponse`.

    class OTVSSMLicenseResponse: NSObject {
    	var licence: Data?
    	var heartbeat: Int=0
    }

### Licence format

The license in instance `OTVSSMLicenseResponse` returned from `license()` callback is the CKC message should pass to `ckcMessage()` as return value.

### Heartbeat callback

In systems where it is supported, the secure session is enforced by a short-duration licence which requires periodic renewal. For other systems, the application uses the heartbeat API on the SSM server. The heartbeat callback prompts the application to use this API and has no parameters or return values. If the callback fails, the application must take any necessary actions

### OTVSSMLicenseResponse

The OTVSSMLicenseResponse is a container class used to hold the full license response (license/ckc data, the heartbeat period and the renewal type)

      /// OTVSSMLicenseResponse may take the values of the license data and the heartbeat period during initialisation.
        /// - Parameters:
        ///   - license: the license/ckc data
        ///   - heartbeat: the heartbeat period in seconds
        ///   - renewType: OTVLicenseRenewType.heartbeat or OTVLicenseRenewType.enforced. How the session is maintained across heartbeat periods, defualt value is OTVLicenseRenewType.enforced
      public init(license: Data? = nil, heartbeat: Int = 0, renewType: OTVLicenseRenewType = OTVLicenseRenewType.enforced) {

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Customization of Subtitle styling

In response to the European Accessibility Act 2025 (EAA), the CONNECT Player SDK provides the ability for customisation of the appearance of text subtitles for HLS content. This allows for enhanced readability where required by the end user through a flexible set of characteristics.

Text of the following subtitle types can be customized:

* SRT

* SMPTE

* WebVTT

Limitation: Closed Captions customization is not supported.

Customization works in conjunction with the iOS/tvOS system settings under the Accessibility settings' "Subtitles \& Captioning" controls. There are a set of predefined standard styling combinations, plus the ability for the user to define their own combinations.  
![Screenshot 2025-05-12 at 09.36.01.png](https://docs.nagra.vision/__attachments/a_336d0a3415bc0e9de71e94a68c6c5dd6ad1201bf48006de5369ad6ed23323e98/Screenshot%202025-05-12%20at%2009.36.01.png?cb=fe20f18bd9e7db48bf5eb492783481dc)
"Subtitles \& Captioning" controls

This feature's configuration options can further refine individual settings. Care should be taken that system settings permit the Video Override for each attribute, for example:  
![Screenshot 2025-05-12 at 09.36.39.png](https://docs.nagra.vision/__attachments/a_505f911599c746a887159c2e3a8465ce513f8efd8ad760f429628ade593eb80e/Screenshot%202025-05-12%20at%2009.36.39.png?cb=ba65288d17bacbeeafc00b987a930203)
Video Override allowed

Customisation of the following styling properties is supported:

1. font size scaling

2. font family

3. text color / text opacity

4. background color / background opacity

5. text edge effect

Each of the attributes are defined `Optional` so can be isolated individually or combined as required.

## API

An API entry point is defined to set these attributes via method call on the player passing an instance of the class `OTVTextStyle` in which each of the the attributes can be set, for example:
Swift

    let txtStyle = OTVTextStyle()
    txtStyle.textSizeScaleFactor = 2.0
    txtStyle.fontFamily = "Times New Roman"
    txtStyle.textColor = UIColor.yellow
    txtStyle.backgroundColor = UIColor.blue.withAlphaComponent(0.75)
    txtStyle.characterEdgeEffect = OTVTextStyleCharacterEdge.uniform

    player.configureTextStyle(styling: txtStyle)

### Text scale factor

Default behaviour is for 24 pt text to be shown. The `textSizeScaleFactor` attribute allows that value to be scaled by the provided factor.

Examples:

* a value of `2.0` will double the rendered size

* a value of `0.5` will half the size

* a value of `1.5` will increase by 50%

* a value of `1.0` (default) will have no effect

It is recommended to integrators that a small set of values are defined and mapped to meaningful labels, for example:

* small: `0.75`

* medium: `1.0`

* large: `1.5`

* extra large: `2.0`

Futhermore it is not recommended going beyond `2.0` as the layout of the rendered subtitles may be greatly affected and readability will likely be affected.  
Bear in mind that 24 pt reflects the default and mid point of the iOS System Accessibility Settings' "Display \& Text Size" control.  
![Screenshot 2025-05-12 at 09.25.38.png](https://docs.nagra.vision/__attachments/a_e0a4dfa1278d1e701445ad62d855a516c03af0be62a623a5077d6420f7c7129a/Screenshot%202025-05-12%20at%2009.25.38.png?cb=86899a90eba0b6abeaa755029557581c)
iOS System Accessibility Settings' "Display \& Text Size"

Both the system scaling and `textSizeScaleFactor` will affect the displayed size. When "Larger Accessibility Sizes" is toggled on the range of options increases further.

### Font family

Default behaviour is for the system settings selected font to be used. The `fontFamily` attribute allows any string font name to be passed.  
It is the application integrator's responsibility to ensure the font is available.

### Text color / opacity

Default behaviour is for the system settings selected text color and opacity to be used.  
Opacity takes a value from `0.0` (transparent) to `1.0` (opaque).

Opacity cannot be overridden without also overriding the color.

Examples:

* `txtStyle.textColor = UIColor.white`

* `txtStyle.textColor = UIColor.yellow`

* `txtStyle.textColor = UIColor.white.withAlphaComponent(0.8)`

### Background color / opacity

Default behaviour is for the system settings selected background color and opacity to be used.  
Opacity takes a value from `0.0` (transparent) to `1.0` (opaque).

Opacity cannot be overridden without also overriding the color.

Examples:

* `txtStyle.backgroundColor = UIColor.black`

* `txtStyle.backgroundColor = UIColor.blue`

* `txtStyle.backgroundColor = UIColor.black.withAlphaComponent(0.8)`

### Character Edge Effect

Default behaviour is for the system settings selected edge style to be used. The set of valid values are defined in the enumeration:
Swift

    public enum OTVTextStyleCharacterEdge: String, Codable {
        case none
        case dropShadowed
        case uniform
        case raised
        case depressed
    }

Examples:

* `txtStyle.characterEdgeEffect = .dropShadowed`

* `txtStyle.characterEdgeEffect = .depressed`

Unlike other platforms there is no native support for character edge color customisation.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Decoder Information

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

This feature provides details about the video and audio decoders supported by the iOS/tvOS devices. Using this information we can optimise media playback and ensure compatibility with different decoder configurations.

**Initialisation**

The initialisation of `OTVDecoderInfoUtil` should be done prior to calling any APIs

    let decoderInfo = OTVDecoderInfoUtil()

**Retrieving information about the supported video decoders**

    let videoDecoderInfoList = decoderInfo.getVideoDecoderInfo()

This will return an array of type `OTVVideoDecoderInfo` , where each element contains the following members:

* decoderName (String): The name of the video decoder.

* decoderMimeType (String): The MIME type of the video decoder.

* maxInstances (Optional Int): The maximum number of instances supported by the decoder.

* decoderProfileLevels (Optional array of Int): Array of supported decoder profile levels.

* isHardwareSupported: Indicates whether hardware acceleration is supported.

* isTunellingSupported: Indicates whether tunnelling is supported.

* supportedMaxResolution (Optional array of Int): Array of supported maximum resolutions.

**Retrieving information about the supported audio decoders**

    let audioDecoderInfoList = decoderInfo.getAudioDecoderInfo()

This will return an array of type `OTVAudioDecoderInfo` , where each element contains the following members:

* decoderName (String): The name of the audio decoder.

* decoderMimeType (String): The MIME type of the audio decoder.

* maxInstances (Optional Int): The maximum number of instances supported by the decoder.

**Verify if a decoder is supported**

    let videoDecoderFormat = OTVDecoderFormat(mimeType: "video/h264")
    let isDecoderSupported = decoderInfo.isVideoDecoderSupported(xFormat: videoDecoderFormat)

    let audioDecoderFormat = OTVDecoderFormat(mimeType: "audio/mp3")
    let isDecoderSupported = decoderInfo.isAudioDecoderSupported(xFormat: audioDecoderFormat)

`isVideoDecoderSupported` and `isAudioDecoderSupported` returns a boolean value indicating whether the given decoder format is supported by the device.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Downloading the SDK

The SDK can be downloaded directly via FTP. Please contact your NAGRA representative to obtain access.

Once access is granted, use this procedure to download the release artefacts:

1. Using your preferred Secure FTP client, log in to **ftp.nagra.com**.

2. Change the directory to the root delivery folder */home/svc-opy_ci/delivery/* and drill down to the appropriate release folder:

   **iOS**

   *com/nagra/opentv/player/sdk/apple/opy-sdk-ios-fps/5.14.1*

   **tvOS**

   *com/nagra/opentv/player/sdk/apple/opy-sdk-tvos-fps/5.14.1*

3. Download the required files.

Your NAGRA FTP account will expire after twelve months, so you will need to refresh your account annually. Accounts are purged from the system 30 days after expiration; after that, they will have to be recreated.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Enabling I-Frame thumbnail previews

Like WebVTT thumbnails, I-Frame thumbnails also show previews of the stream using the time slider/progress bar.

## Prerequisites

A test stream with `#EXT-X-I-FRAME-STREAM-INF` playlists included. The thumbnails example code project provides an example stream.

## Process

To present I-Frame thumbnails to your user, you must create an instance of `OTVThumbnailView`, passing it the stream URL that your `OTVAVPlayer` is (or will be) playing.

`OTVThumbnailView` is only intended for use with streams that have `#EXT-X-I-FRAME-STREAM-INF` playlists as these define where the I-Frames are in the stream. Immediately after initialisation, the function will always return false. If `#EXT-X-I-FRAME-STREAM-INF` playlists are present, the `OTVThumbnailView` will report `true` from its `hasThumbnails()` function once it has determined that they are present (which is an asynchronous operation).

### Presenting the thumbnail to the user

The thumbnail needs to be updated in the `timeSliderDidChange` function, which in the case of I-Frame thumbnails means calling the thumbnail view's `setTime(toSeconds:)` function.  
You cannot create an OTVThumbnailView in storyboard or interface builder as it needs to be initialised with a stream URL.

### DRM considerations

If the stream you are using with `OTVThumbnailView` is encrypted, you will need to have set up a license delegate with the SDK in the same way as for playback of the encrypted stream.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Enabling playback of FPS encrypted content

To enable FairPlay Streaming in your app:

1. Create a new Xcode project and then implement the `OTVLicenseDelegate` protocol to provide the FPS-specific information that the player needs. This delegate needs to authenticate both the stream and the license server.

       final class OTVDelegate: NSObject, OTVLicenseDelegate {
       func contentIdentifier(url: URL) -> Data? {}
       func certificate() -> Data? {}
       func ckcMessage(spc: Data) -> Data? {}
       func scheme() -> String? {}
       }

2. Instantiate an `OTVDRMManager` object in `OPYFPSSDK` to do all the negotiation between `OTVAVPlayer` and your app (via the `OTVLicenseDelegate` implementation).

       private let drmManager = OTVDRMManager:shared

3. Register the delegate to the `OTVDRMManager`:

           private let licenseDelegate = OTVDelegate()
           drmManager.setLicenseDelegate(licenseDelegate)

4. Instantiate an `OTVAVPlayer` using either the content URL or an `OTVAVPlayerItem` object and call its `init()` method:

           private let player = OTVAVPlayer()
           player.init(item)
           /// or:
           private let player = OTVAVPlayer()
           player.init(url)

You should now be able to use the `OTVAVPlayer` object to playback FPS-encrypted content in the same way as with `AVPlayer`.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Enabling playback of FPS encrypted content using OTVSSPLicenseDelegate

To enable FairPlay Streaming in your app using the `OTVSSPLicenseDelegate`:

1. Create a new Xcode project and then instantiate the `OTVSSPLicenseDelegate`. This delegate needs to authenticate both the stream, the licence server and optionally the SSM server if you are using [SSP Secure Session Management](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/secure-session-management.md).

   The code below shows how you would instantiate the `OTVSSPLicenseDelegate` without SSM.

   AppleScript

       let licenseDelegate: OTVSSPLicenseDelegate?

       licenseDelegate = OTVSSPLicenseDelegate(certificateURL: sspCertificateURL, licenseURL: licenseURL)

2. Instantiate an `OTVDRMManager` object in `OPYFPSSDK` to negotiate between `OTVAVPlayer` and your app (via the `OTVSSPLicenseDelegate` implementation).

   AppleScript

       private let drmManager = OTVDRMManager:shared

3. Register the delegate to the `OTVDRMManager`:

   AppleScript

       drmManager.setLicenseDelegate(licenseDelegate)

4. To enable the delegate to request a licence successfully, you need to set a media token. There are two options to do this: `setStream()` and `setToken()`. If your token is static for the playback session, use `setStream()`; however, if you need to update the media token for any subsequent licence request, use `setToken()` .

   * `setStream()` is the simplest way to set the token for the licence request. Use this method if you only intend to use one licence for the associated playback.

     For example, if Airplay is enabled, another licence request will take place and will use the original token (one passed into `setStream()`) to make that licence request.

     AppleScript

         licenseDelegate.setStream(token: sspToken, with: assetURL)

   * `setToken() `triggers a callback whenever a subsequent licence request takes place for the associated playback item. One example is when enabling Airplay; it triggers a licence request and if you need a new media token for this licence request, using this callback is the only way this is possible.

         licenseDelegate?.setToken(initialToken: sspToken, tokenCallback: { () -> String in
                 return self.sspToken
               })

5. Instantiate an `OTVAVPlayer` using either the content URL or an `OTVAVPlayerItem` object and call its `init()` method:

   AppleScript

       private let player = OTVAVPlayer()
       player(item)

       /// or:

       private let player = OTVAVPlayer()
       player(url)

   You should now be able to use the `OTVAVPlayer` object to playback FPS-encrypted content in the same way as with `AVPlayer`.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Enabling WebVTT thumbnail previews

WebVTT thumbnails show previews of the stream using the time slider/progress bar.  
For details of the thumbnail mosaic structure and how it is described in WebVTT, see <https://docs.jwplayer.com/platform/docs>.

## Prerequisites

A test stream with an accompanying set of image files containing a mosaic of thumbnails and the associated WebVTT file describing the thumbnails' timing and placement within the mosaic. The **thumbnails-seeking-webvtt** (or *WebVTT Thumbnails* in UnifiedExampleCode) example code project provides an example stream and image file.

## Example code

To combat memory issues, the maximum number of thumbnails returned is currently limited to 150. We recommend that there should be no more than 150 Standard Definition thumbnails for any asset.

### Preparation

This API footprint for this feature is straightforward; the application needs to implement the `ThumbnailsDelegate` protocol which provides the `prepared()` and `failed()` methods and instantiate the `AssetThumnbails` class.

* The `prepared()` method will be called only when the parsing and downloading of the thumbnails is complete.

* The `AssetThumnbails` class' `thumbnails()` method returns the thumbnails, and importantly, the ownership of the memory for them is transferred with it, so it should not be called again.

For example (from `ViewController.swift`):

    // extend the ViewController class to provide the ThumbnailsDelegate methods
      extension ViewController: ThumbnailsDelegate {
        func prepared() {
          print("Thumbnails have been prepared")
          let thumbPair = thumbnailDownloader.thumbnails()!
          thumbnailHelper = ThumbnailHelper(
            imageMap: thumbPair.thumbnailDictionary,
            cues: thumbPair.startTimes)
        }

        func failed(error: ThumbnailError, message: String) {
          print("Thumbnail error: \(message)")
          thumbnailsEnabled = false
          thumbnailsFailed = true
        }
      }

Then it is a case of calling the `AssetThumbnails` class' `prepareThumbnails()` method supplying the URL of the WebVTT file and a reference to the instance of the delegate, for example:

      // ViewController.swift
      let thumbnailDownloader = AssetThumbnails()
      
      override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Expensive or potentially long running processes
        // should only be started
        // once the view has successfully appeared on screen
        thumbnailDownloader.prepareThumbnails(delegate: self, url: self.thumbnailURL)

### Determining the thumbnail to paint

The prepared map of thumbnails stored above can be queried using the following example methods, which provide the relevant `UIImage` for the input progress in milliseconds.

    // ThumbnailHelper.swift
      class ThumbnailHelper {
      let imageMap: [Int32: UIImage]
      let cues: [Int32]

      init(imageMap: [Int32: UIImage], cues: [Int32]) {
          self.imageMap = imageMap
          self.cues = cues
      }

      // Maps a time in seconds as a float, as returned by
      // OTVAVPlayer, to the closest subsequent thumbnail cue
      // as a time in milliseconds, as returned by the
      // ThumbnailHandler.  

      private func findNearestCue(time: Float) -> Int32 {
      // We multiply the time by 1000 here to go from seconds
      // to milliseconds
      // The thumbnails and cues are returned by the
      //ThumbnailHandler already sorted
          var index = binarySearch(inputArr: cues, searchItem: Int32(time * 1000))
          // Generally an exact cue will not be matched so we
          // will receive an insertion index
          if index < 0 {
              index = ~index
          }
          // Protect against the case where the time seeked
          // to is beyond the last available thumbnail cue
          if index >= cues.count {
              index = cues.count - 1
          }
          return cues[index]
      }

      func findFor(time: Float) -> UIImage? {
          print("Getting thumb for time \(time)")
          let nearestCue = findNearestCue(time: time)
          return imageMap[nearestCue]
      }

### Presenting the thumbnail to the user

The thumbnail needs to be updated in the `timeSliderDidChange` function, which in the case of WebVTT thumbnails would mean calling `ThumbnailHelper.findFor(time:)` to get the correct thumbnail image and updating the UI.

### Teardown

The `AssetThumbnails` class has a `reset()` method which must be called on destruction of the view, for example:

    // ViewController.swift
    thumbnailDownloader.reset()

### Low memory warnings

iOS notifies apps when system memory gets low to free up resources before the system has to end them forcibly. In low memory situations while running, it is recommended that thumbnail fetching is halted and any currently held thumbnails disposed of. This can be done by clearing any references to the thumbnail map in view controllers and calling `AssetThumbnails.reset()` in an appropriate callback where thumbnail fetching is handled.

The following is a simple example of it being handled in a `ViewController`, as in the example code:

    // ViewController.swift
    override func didReceiveMemoryWarning() {
      thumbnailDownloader.reset()
     
      thumbnailHelper = nil
      thumbnailsEnabled = false
      thumbnailsFailed = true
    }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Event timeline

To test the features and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The Event Timeline provides feedback and analysis of performance tracking or playback issues.

## Example code

This feature provides a basic implementation of displaying and filtering events captured by the SDK. The Event Timeline is disabled by default; to enable it:

    OTVEventTimeline.shared.enableTimeline(true)

You can disable the timeline when it is no longer required by passing `false` to the above function.

Functions are available to access the events in the timeline, allowing specific subsets of events to be retrieved or all the events together.

    let allEvents = OTVEventTimeline.shared.getTimelineList()
    let last10Events = OTVEventTimeline.shared.getTimelineList(limit: 10)
    let playbackEvents = OTVEventTimeline.shared.getTimelineList(type: OTVEvent.EventType.playback)
    let dateRangeEvents = OTVEventTimeline.shared.getTimelineList(from: startDate, to: endDate)

To keep the list of events at a manageable size, you can remove events older than a specific `Date`. Passing the current date will clear all events:

    OTVEventTimeline.shared.removeTimeline(olderThan: Date())

There is no limit to the number of events that the timeline will store. Running it for long periods without implementing a means to prune its contents periodically is not recommended.

### OTVEvent

`OTVEvent` comprises a `timestamp`, `type`, `command` and `extra`. The `type` property is populated with one of the static properties of `OTVEvent.EventType`, and the `command` property is similarly populated with one of the static properties of `OTVEvent.EventCommand`. The `extra` property provides additional information that changes depending on the `type` and `command`. It is stored as a JSON `String` that is keyed with one or more of the static properties of `OTVEvent.ExtraKey`.

### OTVEventTimelineAnalyzer

`OTVEventTimelineAnalyzer` can be used to provide a simple analysis of the events in the timeline, including the amount of time it took to start or zap between streams:

    let startTime: Int? = OTVEventTimelineAnalyzer.getStartDuration().first
    let zapTime: Int = OTVEventTimelineAnalyzer.getZpDuration(from: "http://stream1.m3u8",
                                                              to: "http://stream2.m3u8")

The analysis provided by `OTVEventTimelineAnalyzer` is undefined if you are playing back multiple streams at the same time.

### Custom Events

You can add custom events to the timeline to track additional events from your own code.

    OTVEventTimeline.shared.addToTimeline(type: "your-custom-type",
                                         command: "your-custom-command",
                                         extra: "optional-extra-info"

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# External device detection

To test the features and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

Connecting Apple devices to external devices can provide the means to view or capture video content. Here, external devices are any of the following:

* HDMI adaptor allowing the display to an external TV screen

* Screen mirroring

* Airplay

* Screen recording functionality (iOS/iPadOS)

The external device detection feature provides notifications when such external devices are connected and disconnected from an iOS, iPadOS and tvOS device.

## Example code

To enable this feature, you must first initialise the class `OTVOutputDeviceMonitor` with an `OTVAVPlayer` instance.

    OTVOutputDeviceMonitor(player: otvPlayer)

To ensure the lifecycle, your code must retain the `OTVOutputDeviceMonitor` instance. You must set their instance to `nil` to stop device detection on their `OTVPlayer` instance. If there are multiple player instances, each one must be attached to its own instance of `OTVOutputDeviceMonitor`.

Several types of connections are monitored and reported in the notifications `OTVOutputDeviceConnected` and `OTVOutputDeviceDisconnected`. Each one is captured in the `OTVOutputDeviceType` enumeration:
AppleScript

    @objc public enum OTVOutputDeviceType: Int {
      /**
       Digital output consists of HDMI output from HDMI adapters.
       These can be connected to iOS,iPadOS and tvOS devices.
       */
      case digital = 1
      /**
       Airplay output can be setup between an iOS/iPadOS device to a tvOS device.
       */
      case airplay = 2
      /**
       Screen recording can be activated by an iOS/iPadOS device by clicking the recording button from the control panel
       */
      case recording = 3
      /**
       mirroring output can be setup between an iOS/iPadOS device to a tvOS device.
       */
      case mirroring  = 4
      /**
       This is an unknown type of output.
       */
      case unknown = 5
    }

To detect when an external device is connected or disconnected, your code must listen for two notifications:

     NotificationCenter.default.addObserver(self,
                                             selector: #selector(monitoringOutputDeviceConnectionStatus(_:)),
                                             name: .OTVOutputDeviceConnected, object: nil)

     NotificationCenter.default.addObserver(self,
                                             selector: #selector(monitoringOutputDeviceDisconnectionStatus(_:)),
                                             name: .OTVOutputDeviceDisconnected, object: nil)

Your code should handle the notifications for `OTVOutputDeviceConnected` and `OTVOutputDeviceDisconnected` by following these examples:
AppleScript

     @objc func monitoringOutputDeviceConnectionStatus(_ notification: Notification) {
        if let outputDeviceType: OTVOutputDeviceType = notification.userInfo?["outputDeviceType"] as? OTVOutputDeviceType {
          switch outputDeviceType {
          case .digital:
            print("Output Device Monitor: Digital output connected")
          case .airplay:
            print("Output Device Monitor: Airplay output connected")
          case .recording:
            print("Output Device Monitor: Screen being captured")
          case .mirroring:
            print("Output Device Monitor: Screen being mirrored")
          case .unknown:
            print("Unknown output connected")
          @unknown default:
            print("Unknown event")
          }
        }
      }

    @objc func monitoringOutputDeviceDisconnectionStatus(_ notification: Notification) {
        if let outputDeviceType: OTVOutputDeviceType = notification.userInfo?["outputDeviceType"] as? OTVOutputDeviceType {
          switch outputDeviceType {
          case .digital:
            print("Output Device Monitor: Digital output disconnected")
          case .airplay:
            print("Output Device Monitor: Airplay output disconnected")
          case .recording:
            print("Output Device Monitor: Screen not being captured")
          case .mirroring:
            print("Output Device Monitor: Screen not being mirrored")
          case .unknown:
            print("Unknown output disconnected")
          @unknown default:
            print("Unknown event")
          }
        }
      }

The table below outlines the expected behaviour for the output types on iOS/iPadOS and tvOS devices. Analogue output devices are not supported.  

| **Device**  |                                                                                                                           **Case / Expected Behavior**                                                                                                                            |||||
| **Device**  |
|-------------|---------------------------|-------------------------------------------------------------|---------------------------------------------------------------|-------------------------------------------------------------|---------------------------------------------------------------|
| **Action**  | **Digital output device** | **Mirroring**                                               | **Airplay**                                                   | **Screen Recording**                                        |
| iPhone/iPad | Connection/on             | Notification: `OTVOutputDeviceConnected` Type: `digital`    | Notification: `OTVOutputDeviceConnected` Type: `mirroring`    | Notification: `OTVOutputDeviceConnected` Type: `airplay`    | Notification: `OTVOutputDeviceConnected` Type: `recording`    |
| iPhone/iPad | Disconnection/off         | Notification: `OTVOutputDeviceDisconnected` Type: `digital` | Notification: `OTVOutputDeviceDisconnected` Type: `mirroring` | Notification: `OTVOutputDeviceDisconnected` Type: `airplay` | Notification: `OTVOutputDeviceDisconnected` Type: `recording` |
| AppleTV     | Connection/on             | Notification: `OTVOutputDeviceConnected` `T`ype: `digital`  | Not supported                                                 | Not supported                                               | Not supported                                                 |
| AppleTV     | Disconnection/off         | Notification: `OTVOutputDeviceDisconnected` Type: `digital` | Not supported                                                 | Not supported                                               | Not supported                                                 |

Once `OTVOutputDeviceMonitor` is initialised, your code will receive notifications whenever the output device is connected and disconnected. If a connection is started before the class is initialised and remains connected, an `OTVOutputDeviceConnected` will be fired.

The same applies when the application goes into the background or comes into the foreground on the device. If the output is stopped or started in the background, a corresponding notification will be fired, but only when returning to the foreground. For example, if you have initialised `OTVOutputDeviceMonitor` and the app is sent to the background, the end-user begins screen recording on the device. When the application returns to the foreground, a notification will be fired for `OTVOutputDeviceConnected` of type `recording`.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Get licence information from MediaLive Multidrm server

Please refer to [MediaLive platform API](https://docs.nagra.com/opentv-platform-2.x-documentation/current/Default/online-api).

## HTTP URL

This API `mdrmService.isContentAuthorised` is called by sending a HTTP GET request to: *http://\<server\>:\<port\>/hue-gateway/gateway/http/js/mdrmService/isContentAuthorised* .

The endpoint (server, port) is configurable in MDRM-Manager.

### HTTP headers

This SDP service requires HTTP basic authentication.  

|     **Name**     | **Type** |                                                                          **Description**                                                                          | **Always Present** |
|------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
| x-correlation-id | string   | A unique identifier to correlate logs corresponding to a request through the entire Multiscreen system.                                                           | Yes                |
| Accept           | string   | Standard HTTP header. Will include `application/json` if supplied.                                                                                                | No                 |
| `arg0`           | string   | Content ID; The unique identifier assigned to the content by the CMS                                                                                              | Yes                |
| arg1             | string   | Private Data; Data passed from the client in the JSON format. This argument contains the user device zone location.                                               | No                 |
| `token`          | string   | A token acquired during client sign-on. The Portal decrypts the authToken and retrieves the account and/or device information required for license authorization. | Yes                |

### URI Parameters

#### **Example request URI**

XML

     http://mySDPServer.com:80/hue-gateway/gateway/http/js/mdrmService/isContentAuthorized?token=zxcvb&arg0=12345678&arg1={"locality": "XYZ", "isDownload": true}

### Body

The body of this request is empty.

### Response

HTTP 200 with JSON payload.  

|     **Name**      |                    **Type**                     |                                                                                      **Description**                                                                                       |        **Always Present**         |
|-------------------|-------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------|
| `isAuthorized`    | String, always "AUTHORIZED" or "NOT_AUTHORIZED" | The authorization status of the request.                                                                                                                                                   | Yes                               |
| `accountNumber`   | string                                          | Unique identifier for the client account.                                                                                                                                                  | No (Yes for AUTHORIZED responses) |
| `usageRules`      | UsageRulesType (see below)                      | The criteria for creating the license. Present only for Authorized responses.                                                                                                              | No                                |
| `additionalInfo`  | JSON map of strings                             | Optional parameter to convey additional information if required. The contents are a JSON map, i.e. key-value pairs. SPD currently uses this field to present one parameter, "casProductId" | No                                |
| keyDeliveryWindow | KeyDeliveryWindow (see below)                   | If 'Live PPV License' is enabled, when no live subscription exists for the requested DRM ID, PPV event timings will be included. For other scenarios, keyDeliveryWindow will be NULL.      | Yes                               |

### UsageRulesType

|             **Name**              |              **Type**               |                                                            **Description**                                                             |                                                 **Always Present**                                                 |
|-----------------------------------|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------|
| ViewingNumber                     | integer                             | The viewing number                                                                                                                     | No                                                                                                                 |
| expiryDate                        | long                                | The time at which the license should expire. Milliseconds since Epoch                                                                  | No                                                                                                                 |
| startDate                         | long                                | The start time of the license. Milliseconds since Epoch                                                                                | No                                                                                                                 |
| consumptionWindow                 | string                              | The content consumption window.                                                                                                        | No                                                                                                                 |
| LicenseExpiryDuration             | integer                             | The expiry duration of the license.                                                                                                    | No                                                                                                                 |
| isStorageAllowed                  | boolean                             | A flag stating whether persistence of the entitlement is allowed on the device.                                                        | No                                                                                                                 |
| fixedRuleset.digitalOutputBitrate | float: a value in increments of 0.1 | The capped bitrate, when digital output is "BestEffortWithBitrateCapping", where the value is defined in Mbits/s in increments of 0.1. | Only when digitalOutput is "BestEffortWithBitrateCapping" This property is not currently part of the SDP response. |
| fixedRuleset.analogOutputBitrate  | float: a value in increments of 0.1 | The capped bitrate, when analogOutput is "BestEffortWithBitrateCapping", where the value is defined in Mbits/s in increments of 0.1.   | Only when analogOutput is "BestEffortWithBitrateCapping" This property is not currently part of the SDP response.  |
| isViewingWindowFloating;          | boolean                             | Boolean value to enable/disable floating viewing window (usually possible when the licence is stored in the device)                    | No                                                                                                                 |

### Key delivery window

| **Name**  | **Type** |               **Description**               | **Always Present** |
|-----------|----------|---------------------------------------------|--------------------|
| beginDate | long     | PPV event's start time - guard padding time | Yes                |
| endDate   | long     | PPV event's end time + guard padding time   | Yes                |

Example authorized response
XML

    {
        "resultCode": "0",
        "result": {
            "isAuthorized" : "AUTHORIZED",
            "accountNumber" : "12345",
            "keydeliverywindow": {
                "beginDate" : "1234236612725",
                "endDate" : "1234556667775",
            },
            "usageRules": {
                "viewingNumber" : 12345,
                "expiryDate" : "1234556667775",
                "startDate" : "1234556667775",
                "consumptionWindow" : 1000,
                "licenseExpiryDuration" : 200,
                "isStorageAllowed" : true
            },
            "casProductId" : "IQ3-ASSET-VOD"
        }
    }

Example NOT authorized response

    {
        "resultCode": "0",
        "result": {
            "isAuthorized" : "NOT_AUTHORIZED"
        }
    }

### Error response

HTTP error response with JSON payload  

|    **Name**     | **Type** |                **Description**                | **Always Present** |
|-----------------|----------|-----------------------------------------------|--------------------|
| `resultCode`    | string   | SDP Error code.                               | Yes                |
| `result`        | string   | A text message explaining the error           | Yes                |
| `localeMessage` | string   | A localized text message explaining the error | No                 |

Example response

    {
        "resultCode": "85102",
        "result": "Service method mdrmService.isDeviceAuthorised throw exception \"tv.quative.service.ServiceException: ErrorCode: [code=85102, severity=ERROR] Unable to decrypt the token, token is invalid\"",
        "localeMessage": "none"
    }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Initial persistency set-up

The application must access the `OTVPersistenceManager` and set up the listeners; this only has to be done once, before any other persistence operation. Calling `OTVPersistenceManager.sharedManager()` for the first time initialises the `OTVPersistenceManager` object. The manager can then discover persistent assets stored in previous runs of the application.

## Example code

### Create or access the download manager

The following code example shows how to gain access to the download manager.

    let dlManager = OTVPersistenceManager.sharedManager

### Register listeners

To follow the state and progress of downloads, listeners (observers) should be attached with the `NotificationCenter`.

    NotificationCenter.default.addObserver(self,
                                            selector: #selector(self.storeStateNotification),
                                            name: .OTVAssetDownloadStateChanged,
                                            object: nil)

      NotificationCenter.default.addObserver(self,
                                            selector: #selector(self.storeProgressNotification),
                                            name: .OTVAssetDownloadProgress,
                                            object: nil)

      @objc func storeStateNotification(notification: NSNotification) {
        if let info = notification.userInfo as? [String: Any] {
          let state = info[OTVPersistenceAsset.Keys.downloadState] as? String
          let name = info[OTVPersistenceAsset.Keys.name] as? String
        }
      }
      
      @objc func storeProgressNotification(notification: NSNotification) {
        if let info = notification.userInfo as? [String: Any] {
          let title = info[OTVPersistenceAsset.Keys.name] as? String
          let percent = info[OTVPersistenceAsset.Keys.percentDownloaded] as? Double
        }
      }

**Next step:**

* For clear content, you can [start the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/starting-the-download.md).

* For FPS encrypted content, you may need to [retrieve the licence](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/retrieving-the-licence.md) if the content identifier is known. Otherwise, you need to create an instance of `OTVLicenseDelegate` and set it to `OTVPersistenceManager` to [start the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/starting-the-download.md).

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Insight analytics

The application can report player metrics to the Insight servers using the Insight Agent wrapper class. The wrapper class uses the Insight Agent to manage Insight sessions alongside playback, during which content information and playback monitoring information is collated and uploaded to the Insight servers. A session lifecycle equates typically to the playback of one content, starting when the content is loaded into the player and ending when the content is unloaded.

The application is responsible for:

* Instantiating and driving the player

* Providing metadata about the content

The Insight Agent wrapper is responsible for:

* Observing metrics and events on the player

* Reporting metrics and events to the Insight servers

## Exposed classes in Insight Agent wrapper

The following classes are exposed inside the Insight Agent wrapper for the application:

* **OTVInsightAgent**

  This is the main Insight Agent wrapper class. The application instantiates this class and calls the exposed APIs.

* **OTVInsightConfig**

  The instance of this class used to pass the Insight configuration information to OTVInsightAgent.

* **ContentInfoHolder**

  The instance of this class used to pass the content information to OTVInsightAgent.

* **UserInfoHolder**

  The instance of this class used to pass the user information to OTVInsightAgent.

## Example code

### Insight Framework dependency

To enable this feature, the application needs the Insight Framework library **InsightAgent.xcframework** *,* which is delivered separately.

### Insight configuration Information

The application needs the following Insight configuration information to create an instance of **OTVInsightConfig** class.

      "insightCollectorURL" : "https://collector.insight-stats.com/api/v1/",
      "samplingInterval" : "10",
      "reportingPeriod" : "30",
      "appName":"OpenTV Player Sample App",
      "appVersion":"5.x",
      "deviceType":"handheld",
      "operatorId":"9c703ed0309f"

### Content information

The following information is required to create an instance of *ContentInfoHolder* class. Some of the parameters with `?` are optional.

#### Live content

    channelId: String,   
    channelName: String,
    eventId: String, 
    eventName: String, 
    genre: [String]?, 
    bitrates: [Int]?,
    uri: String?,
    duration: Int?,
    type: String 

#### VOD content

    contentId: String,
    contentName: String,
    genre: [String]?,
    bitrates: [Int]?,
    uri: String?,
    duration: Int?

### User information

The following information is required to create an instance of **UserInfoHolder** class.

    userId: String,
    accountId: String,
    fullName: String,
    gender: String,
    age: Int,
    ageRange: String,
    category: String,
    street: String,
    city: String,
    state: String,
    postCode: String,
    country: String

### Creating an Insight Agent wrapper instance

The application can create the Insight Agent wrapper instance in the correct place as follows:

    mInsightAgent = new OTVInsightAgent(xConfig);

Here *xConfig* is the object of *OTVInsightConfig* class. The same Agent can be reused for multiple playback sessions during zapping, so there is usually no need to instantiate this class more than once.

#### Preparing the Insight configuration instance

The OTVInsightConfig instance is prepared as follows:

    var xConfig = OTVInsightConfig(collectorURL: "https://collector.insight-stats.com/api/v1/",
                                   deviceId: "iOSTestDeviceId", 
                                   operatorId: "9c703ed0309f", 
                                   samplingInterval: 10, 
                                   reportingInterval: 30, 
                                   framedropsEnabled: true, 
                                   deviceType: OTVDeviceType.handheld,
                                   appName: "Insight Reference Application",
                                   timezone: "Europe/Zurich")

### Session control

The *OTVInsightAgent* is only responsible for collating the analytics data; it needs the application to control an Insight session alongside playback. The application should start the session to align with the playback of the stream.

* For both live and VOD, a session should start when playback commences.

* For VOD streams, a session should stop when the playback finishes, whether by error, user action or play-out.

* A session should end for live streams when the stream is switched or stopped by an error.

#### Prepare content Information

When starting the session, the application should prepare the content information the session will need; this includes metadata related to the content.

    var contentInfoHolder = ContentInfoHolder(channelId: "ChannelID-1",
                                              channelName: "ChannelName-1",
                                              type: "VOD")

#### Preparing user information

When starting the session, the application should prepare the user information which the session will need; this includes metadata related to the user account.

    var userInfoHolder = UserInfoHolder(userId: "user id", 
                                        accountId: "account id", 
                                        fullName: "Full Name", 
                                        gender: "Male", 
                                        age: 20, 
                                        ageRange: "10-30", 
                                        category: "Category",
                                        street: String, 
                                        city: String, 
                                        state: String, 
                                        postCode: String, 
                                        country: String)

#### Start session

After getting all the content information and user information available, the application can start the session with OTVAVPlayer instance, content information and user information.

    mInsightAgent.startSession(xPlayer: otvPlayer, xContentInfoHolder: contentInfoHolder, xUserInfoHolder: userInfoHolder)

#### Stop session

The application can stop the session when playback is stopped or zapping to another content before starting the session.

    mInsightAgent.stopSession()

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Logging in production builds

The application can enable logging, which is not enabled by default, in production builds by implementing a class conforming with a protocol `IOTVLogProvider` exposed by SDK. The application implements the `logProvider()` API in this class, which is called with log information by SDK framework for the application to process that log. The application needs to pass the instance of this class using API `setLogProvider()` in SDK framework.

## Exposed protocol and API in the SDK

    // The protocol defines a method that application need to implement to receive the log from SDK in production build.
    @objc public protocol IOTVLogProvider: NSObjectProtocol {
        
        func logProvider(xLog: String)

    }

    // Set the instance of a class conforming to IOTVLogProvider protocol.
    func setLogProvider(xLogProvider: IOTVLogProvider);

## Steps to enable log production

### Implementing a class conforming to protocol IOTVLogProvider

Below is the sample class implementation conforming to the protocol `IOTVLogProvider `.

    public class AppLogProvider: NSObject , IOTVLogProvider {

        public func logProvider(xLog: String) {
            // Application to process this log.
            print("[APP]: \(xLog)")
        }
    }

### Creating an instance of the class and setting it to the SDK framework

The application can create the instance of the log provider class and set it to the SDK.

    //Create the instance of log provider class.
    let logProvider = AppLogProvider()

    // Set the appropriate log level in SDK
    OTVSDK.setLogging(level: .debug)  //possible log level: .debug, .info, .warning, .error

    //Set the instance of log provider.
    OTVSDK.setLogProvider(xLogProvider: logProvider)

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Multi-instance

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The CONNECT Player SDK for FPS supports multiple instances of `OTVAVPlayer` being created and used simultaneously. This allows features such as the main stream playback to be shown whilst a preview window of another stream is shown to the user.

To use multiple players for clear streams, create two players and attach each to its own `AVPlayerLayer` (or `OTVContainerView`) or `VideoPlayer` view in SwiftUI. By doing this, you will get independent playback and control for each player.

Multi-instance playback for encrypted streams is slightly more complex, requiring additional information from the license delegate.

## Procedure

Example code of multiple player instance support can be found in the multi-instance folder (or UnifiedExampleCode written in SwiftUI).  
In the `ViewController` class (or Multi Instance model class in UnifiedExampleCode), the example code shows how to set up the license delegate and provide the SSP token to it and how to set the license delegate to `OTVDRMManager` or `OTVAVPlayer` instance. The example has two encrypted streams with different stream tokens used to fetch the licence.

To support the playback of multiple encrypted streams on multiple players simultaneously, two instances of license delegate are created to handle the license requests for each player differently.

* For the first player instance, the license delegate is set to `OTVDRMManager` by calling `OTVDRMManager.shared.setLicenseDelegate(delegate)` and the same procedure as only one player instance can be followed.

* For the second player instance, the player MUST be constructed with `let player = OTVPlayer()` and the second license delegate MUST be set to player by calling `player.setLicenseDelegate(delegate)` before playing any stream.

The way of setting delegate to player can also be used for only one player instance, which can achieve the same goal as calling `OTVDRMManager.shared.setLicenseDelegate(delegate).`

When zapping, for each player, the token of the string MUST be set before the stream is set to the player. The following code demonstrates the sequence:

    licenseDelegate.setHTTPHeader(parameters: ["nv-authorizations": token])
    player.replaceCurrentItem(with: OTVAVPlayerItem(url: streamUrl))

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Network statistics

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The CONNECT Player SDK provides classes and interfaces enabling you to access statistics concerning the player and network.

    public class OTVNetworkAnalytics: NSObject {
    	public var adaptiveStreaming: AdaptiveStreaming
    	public var networkUsage: NetworkUsage
    	public var contentServer: ContentServer
    }

Network errors and http error code and message.  

    public class OTVNetworkAnalytics: NSObject {
    	// Error code, -1001 indicates other network error; 400 indicates http error, check httpError for status code 
      	public internal(set) var error: Int
    	// HTTP response status code when gets http error
      	public internal(set) var httpError: Int {
    	// HTTP error message
      	public internal(set) var httpErrorMessage: String?
    }

Network analytics related to adaptive streaming.  
C++

    public protocol AdaptiveStreaming {

     /** 
      Returns an array of bitrates available in the playlist
      - Returns: an array of bitrates available in the playlist, or nil if unknown
      - Note: This function will make a seperate request for the Master playlist to return all the availableBitrates. This request is only done once you call this           function and only on the first time you call it for that contnet. Every subsiquent call on the same content does not requst the playlist again. 
     */
      func availableBitrates() -> [Int]?

      /**
       Returns the bitrate from the playlist that has been selected for playback, in bits per second
       - Returns: the bitrate from the playlist that has been selected for playback, or 0 if unknown
      */
      func selectedBitrate() -> Double

      /**
       Returns the number of times the bitrate has switched.
       - Returns: the number of times the bitrate has switched.
      */
      func bitrateSwitches() -> Int

      /**
       Returns the number of times the bitrate has switched to a lower bitrate.
       - Returns: the number of times the bitrate has switched to a lower bitrate.
      */
      func bitrateDowngrade() -> Int

      /**
       Returns the Video and Audio track's average bit rate, in bits per second.
       - Returns: the Video and Audio track's average bit rate, in bits per second.
      */
      func averageBitrate() -> Int

      /**
       Returns the video track's average bit rate, in bits per second.
       - Returns: the  video track's average bit rate, in bits per second.
      */
      func averageVideoBitrate() -> Int

      /**
       Returns the audio track's average bit rate, in bits per second.
       - Returns: the  audio track's average bit rate, in bits per second.
      */
      func averageAudioBitrate() -> Int
     
    }

Network analytics related to network usage.  
C++

    public protocol NetworkUsage {

      /**
       Returns the number of bytes downloaded so far
       - Returns: the number of bytes downloaded so far
       */
      func bytesDownloaded() -> Int64

      /**
       Returns the empirical throughput, in bits per second, across all media downloaded
       - Returns: the throughput, in bits per second
       */
      func downloadBitrate() -> Double

      /**
       Returns the video track's average bit rate, in bits per second
       - Returns: the video track's average bit rate, in bits per second
       */
      func downloadBitrateAverage() -> Double

      /**
       Returns the number of media read requests from the server to this client
       - Returns: The number of media read requests from the server to this client or -1 if unknown
      */
      func numberOfMediaRequests() -> Int

      /**
       Returns the accumulated duration, in seconds, of active network transfer of bytes
       - Returns: the accumulated duration, in seconds, of active network transfer of bytes or -1 if unknown
      */
      func transferDuration() -> TimeInterval
      /**
       Returns the total number of times that downloading the segments took too long.
       - Returns: the total number of times that downloading the segments took too long or -1 if unknown
      */
      func downloadsOverdue() -> Int
    }

Network analytics related to the content server.  
C++

    public protocol ContentServer {
      /**
       Returns the IP address of the content server
       - Returns: the IP address of the content server for the last segment, or nil if unknown
       */
      func finalIPAddress() -> String?

      /**
       Returns the URL of the selected playlist, after any redirects
       - Returns: the URL of the selected playlist, or nil if unknown
       */
      func finalURL() -> String?

      /**
       Returns the original URL of the stream
       - Returns: the original URL of the stream, or nil if unknown
       */
      func url() -> String?

      /**
       Returns a count of changes to the server address over the last uninterrupted period of playback.
       - Returns: the number of server address changes or -1 if unknown
      */
      func numberOfServerAddressChanges() -> Int
    }

Notification and all event type.  

    public static let OTVNetworkAnalyticsNotification = Notification.Name("OTVNetworkAnalyticsNotification")
    public enum Event: Int {
        case selectedBitrateChanged = 0
        case availableBitratesChanged = 1
        case urlChanged = 2
        case errorChanged = 3
      }

Observe the OTVNetworkAnalyticsNotification and get the event.  

    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handleNetworkAnalyticsNotification(notificaition:)),
                                           name: .OTVNetworkAnalyticsNotification,
                                           object: nil)

    func handleNetworkAnalyticsN
    let selectedBitrate = otvplayer?.networkAnalytics?.adaptiveStreaming.selectedBitrate() {
    					// Handle selectedBitrate
    				}
    			case .availableBitratesChanged:
    				if let availableBitrates = otvplayer?.networkAnalytics?.adaptiveStreaming.availableBitrates() {
    					// Handle availableBitrates
    				}	
    			case .urlChanged:
    				if let url = otvplayer?.networkAnalytics?.contentServer.url() {
    					// Handle url
    				}
    			case .errorChanged:
    				if let error = otvplayer?.networkAnalytics?.error {
    					if error = -1001 {
    						// Other network error
    					} else if error == 400, 
    						let httpError = otvplayer?.networkAnalytics?.httpError,
    						let httpErrorMessage = otvplayer?.networkAnalytics?.httpErrorMessage {
    						// Handle http error code and http error message
    					}
    				}
    			}
    		}
    	}
    }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Observing player errors

## Observing player errors using the OTVAVFoundationErrors notification

When `OTVAVPlayer` and `OTVAVPlayerItem` encounter an error when initiating or playing content, it produces a notification using the `OTVAVFoundationErrors` notification.

    /// Notification when OTVAVPlayer or OTVAVPlayerItem encounters an error.
    /// which gives the statusCode, domain and message for the error encounted. 
    public let OTVAVFoundationErrors = Notification.Name("otvAVFoundationErrors")

Two types of OTVAVFoundationErrors are encountered: OTVAVPlayer and OTVAVPlayerItem.

    /**
     `OTVAVFoundationError` is an enum that represent they type of OTVAfoundation type either, OTVAVPlayer or OTVAVPlayerItem
     */
    public enum OTVAVFoundationError: Int {
      // AVPlayerError
      case otvAVPlayer = 1101
      // AVPlayerItemError
      case otvAVPlayerItem = 1102
    }

Listening to `OTVAVFoundationErrors` can be done by first registering to listen to the notification.

    NotificationCenter.default.addObserver(self, selector: #selector(opyPlayerErrorsReturned), name: OTVAVFoundationErrors, object: nil)

The user filters on the type of `OTVAVFoundationError` returned (either `OTVAVPlayer` or `OTVAVPlayerItem`), then uses the information returned from the userInfo object of the notification. The userInfo object is a dictionary containing three keys, statusCode, domain and message.

    @objc func opyPlayerErrorsReturned(_ notification: NSNotification) {
      if let error = notification.object as? OTVAVFoundationError, let userInfo = notification.userInfo {
          let statusCode = userInfo["statusCode"]
          let domain = userInfo["domain"]
         let messsage = userInfo["message"]
          switch error {
                case .otvAVPlayer:
                      print("OTVAVPlayer errror:: Status code = ", statusCode, " domain = ", domain, " message = ", messsage)
                case .otvAVPlayerItem:
                      print("OTVAVPlayerItem errror:: Status code = ", statusCode, " domain = ", domain, " message = ", messsage)
                @unknown default:
                        NSLog("Unexpected OPY error: \(error)")
                }
          }
    }

In the case of otvAVPlayerItem errors, the errors returned are that of `AVPlayerItem` error codes returned during playback when the item fails to play, or the item status changes to failed state.

## Observing player errors using the AVPlayerItemNewErrorLogEntry notification

To listen to `AVPlayerItemNewErrorLogEntry` for the content being played, create a notification observer on `AVPlayerItemNewErrorLogEntry` using the `OTVAVPlayerItem` of the content being played.

    NotificationCenter.default.addObserver(self, selector: #selector(handleAVPlayerItemErrorLog),
                                               name: NSNotification.Name.AVPlayerItemNewErrorLogEntry,
                                               object: self.playerItem)

This notification is triggered every time the player item encounters a media error. You can filter on the last error log event using the code below.

     private func latestPlayerItemErrorLogEvent() -> AVPlayerItemErrorLogEvent? {
        guard let item = playerItem, let errorLog = item.errorLog() else {
          return nil
        }
        return errorLog.events.last
      }

An example of how to handle the information returned from the `AVPlayerItemNewErrorLogEntry` notification. All errors returned through this notification are from `AVFoundation` only and do not contain any `OPYPlayback` errors.

      @objc func handleAVPlayerItemErrorLog(notification: Notification) {
        if let statusCode = latestPlayerItemErrorLogEvent()?.errorStatusCode, let message = latestPlayerItemErrorLogEvent()?.errorComment,  let domain = latestPlayerItemErrorLogEvent()?.errorDomain {
          print("The status code is: ", statusCode, " the error domain is: ", domain, " the error message is: ", message)
        }
      }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Offline playback

Offline playback is only available for iOS, not for tvOS. To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

This feature enables you to store content and licenses for future playback. Offline, or persistent storage, is achieved using two classes. The `OTVPersistenceManager` object provides all the persistence operations required by the application. It maintains a list of `OTVPersistenceAsset` assets, either downloading or where the download has completed. The app can monitor the status of the various downloaded/downloading assets by setting up listeners (observers) for download progress events and changes in the download state.

Offline playback is enabled in the following stages:

* Offline playback of clear content

* Offline playback of FPS encrypted content

Due to how Apple handles background downloading tasks/processes, unexpected behaviour can occur when connected to the Xcode debugger when downloading offline content. If any unexpected behaviour occurs when connected to Xcode, re-test without connection to the Xcode debugger.

## Prerequisites

### Clear content

* The application is configured to play clear content.

* A clear stream with download enabled is available for testing.

### FPS encrypted content

* The application is configured to play FPS encrypted content.

* An encrypted stream with a download-enabled license is available for testing. This can either be one for which you have the content identifier already or one that has the content id embedded within the `EXT-X-KEY` or `EXT-X-SESSION-KEY` tag of the HLS playlists.

## Background downloading

The SDK allows HLS video downloads to run in a background thread, even if an app is suspended or terminated under certain conditions.

After an app is terminated, the SDK can recover downloads in progress in the following cases:

* App termination due to memory pressure in the foreground.

* App termination due to high memory usage when the app is suspended.

* App crashes (null pointers, exceptions, etc.).

* App termination while suspended due to limited system resources.

* Running with Xcode (when Xcode terminates the process).

* Termination via the App Switcher (for example, on devices with a home button, a double-home-press and slide up).

* Termination while the app is suspended and the device reboots.

## Process

The full procedure comprises the following steps:

* [Initial persistency set-up](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/initial-persistency-set-up.md)

  The application gets access to `OTVPersistenceManager` and sets up the listeners.

* [Retrieving the licence](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/retrieving-the-licence.md) (FPS encrypted content only)

  The application may have to request the licence ahead of the download being triggered.

* [Start the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/starting-the-download.md)

  The application triggers the effective download and persistency of the media data.

* [Play the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/playing-the-download.md)

  The application can playback the downloaded asset in a similar manner to streamed content.

* [Renewing a licence](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/renewing-a-licence.md)

  The application checks the licence duration and renews it when it expires.

* [Purging the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/purging-the-download.md)

  The application purges the asset and licence (if applicable) when the user no longer needs it.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Playback of clear content

## SDK lifecycle

The SDK is created and initialised when the client application starts up (upon calling `OTVSDK.load()`) and is destroyed when the application terminates. Typically, the SDK is created by the following code within the application entry `AppDelegate::application(application:` `didFinishLaunchingWithOptions:)` method.

    OTVSDK.load()

## Enabling logging

SDK users can set the logging levels by calling `OTVSDK.setLogging(level:)` after the `OTVSDK.load()` is called. If `OTVSDK.setLogging()` is not called, the default level is `.warning`.

    OTVSDK.setLogging(level: .debug)

| **Level** |                                                          **Description**                                                           |
|-----------|------------------------------------------------------------------------------------------------------------------------------------|
| .error    | Indicates a situation requires investigation, which may or may not lead the application to abort.                                  |
| .warning  | Indicates a potentially harmful situation that may lead to an error.                                                               |
| .info     | Designates information about data values or object states at a coarse-grained level.                                               |
| .debug    | Designates fine-grained informational events, especially the key APIs calling traces that are most useful to debug an application. |

## Low latency support

As OTVAVPlayer uses the underlying Apple AVPlayer, SDK 5 supports HLS clear and encrypted low-latency playback. From an OTVAVPlayer perspective, nothing is required to enable low-latency playback when a low-latency-capable stream is encountered. For details on how to set up your HLS streams to enable low-latency playback, see the Apple document [Enabling Low-Latency HTTP Live Streaming (HLS)](https://developer.apple.com/documentation/http_live_streaming/enabling_low-latency_http_live_streaming_hls).

Low-latency extensions are defined in the HLS specification [HTTP Live Streaming 2nd Edition](https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis) revision 7 and later.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Playback of FPS encrypted content

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The `OTVAVPlayer` class extends the native `AVPlayer` class and makes it easy to enable FPS playback. It extends all of the functionality of `AVPlayer`, and because FPS is the native DRM, it can support HD and 4K playback.

This page provides an overview of the tasks required to enable FairPlay Streaming in your application, with code examples to show how to implement licence delegation and the framework used to playback encrypted content.

## Prerequisites

An understanding of Apple's FairPlay technology; see the Apple page [FairPlay Streaming](https://developer.apple.com/streaming/fps/).

The certificate provided by Apple when an operator wants to use FPS by submitting an X.509 Certificate Signing Request linked to the private key. This certificate will be used when requesting a key request to the OS (returned with Server Playback Context (SPC)).

Key Server Module (KSM)

A handheld device. Playback of FPS encrypted streams is not supported for device simulators.

## Example code

The **encrypted-playback-advanced** (or *Encrypted Playback Advanced* in UnifiedExampleCode) example code contains an Xcode project demonstrating the entire process for acquiring a license for, and playing back, an encrypted HLS stream. All tokens and URLs in the example code are hard-coded, but they might be fetched from external sources in real applications. The license server used in this example is a NAGRA Security Services Platform (SSP) head-end.

The app has an `SSPFairplayLicenseDelegate` class, which is an SSP-specific implementation of an `OTVLicenseDelegate`. Alongside this class, other helper classes assist the handling of licenses, tokens and certificates used to enable FPS playback:

* `ContentIdentifierGenerator` associates a URL with its `ContentId`.

* `FairPlayApplicationCertificateDownloader` acquires the authentication certificate. Apple issues this certificate to the license provider; it later authenticates the association between the license server and the player.

* `PlayerCertificateAuthenticator` downloads and stores the SSP Certificate that authenticates the player against the license server (using `FairPlayApplicationCertificateDownloader`). This certificate only needs to be acquired once.

* `StreamLicenceAuthenticator` authenticates an encrypted stream and provides its license in the form of an encrypted CKC message. The content token used in this authenticator is an SSP-specific implementation. It authenticates the `contentId` and may have additional information such as license duration and expiry. As authentication needs to be done for each stream, you will likely instantiate this class every 'zapping'.

The central part of enabling playback of FairPlay-encrypted content is to implement the following methods of the `OTVLicenseDelegate` protocol:

* `contentIdentifier()` given a licence server URL (the URI specified in the playlist's `EXT-X-KEY` tag), this should get and return the content identifier used on the server side.

* `certificate()` fetches and returns the application certificate. Apple supplies this and is used with the content identifier to request a key request from the OS.

* `ckcMessage()` fetches and returns the Content Key Context (CKC) message that contains the encrypted key used to decrypt the FairPlay stream.

* `scheme()` should return the name of the URI scheme (specified in the `EXT-X-KEY` URI).

The exact contents of each method will depend on the licence server used.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Playback of linear adverts

In SwiftUI-based applications, the code snippet in the` ViewController` class is implemented in the feature's model file.

To enable playback of linear adverts.

1. In `ViewController.swift`, add the following imports.

       import OPYSDKFPS
       import GoogleInteractiveMediaAds

2. Update the View Controller to specify the configuration of the required adverts in an ad tag URL, for example:

       class ViewController: UIViewController {
        
        let otvPlayer : OTVAVPlayer
        @IBOutlet weak var playerView: PlayerView!
        let assetURL = URL(string:
          "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_clear/index.m3u8")!
        
        //VMAP Pre-, Mid-, and Post-rolls, Single Ads
        let adTagURL = "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480" +
                      "&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&" +
                      "gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26" +
                      "sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=;"
        ...

3. After the `init` method, add an attribute to access the `IMAWrapper`.

       // IMAWrapper that is used to manage the IMA Google Framework
       var imaWrapper: IMAWrapper?

4. In the same file, implement the `IMAWrapperDelegate` protocol. This allows the `IMAWrapper` class's functionality to be triggered for advert management. Do this by extending the `ViewController` class, for example:

       // ViewController must adopt the protocol IMAWrapperDelegate
       // so the IMAWrapper can access player fuctions.
       extension ViewController: IMAWrapperDelegate {
         func pauseContent() {
           print("IMAWrapper: pause video to show ads")
           otvPlayer.pause()
         }
        
         func resumeContent() {
           print("IMAWrapper: resume video")
           otvPlayer.play()
         }
        
         func allAdsCompleted() {
           print("IMAWrapper: all ads completed")
         }
        
         func log(event: String?) {
           print("IMAWrapper: AdsManager error: \(event ?? "empty message")")
         }
       }

5. Add the `viewDidAppear` method, with the following:

   * Instantiate the `IMAWrapperAdsSettings` class to override default Google IMA configuration.

   * Instantiate the `IMAWrapperPlayerDetails` class which allows the provision of the UI elements and details of the Ad Tag URI to the `IMAWrapper`.

   * Instantiate the `IMAWrapper` class, providing a reference to the class that implements the `IMAWrapperDelegate` protocol. In this example, `ViewController` implements the protocol so we pass a reference to self.

   * Call `requestAds()` to start the ads.

   Your `viewDidAppear` method should look like the following:

       override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // Customise the settings of the IMAWrapper see API documention for details
        let setupSettings = IMAWrapperAdsSettings(settingsDictionary: [String: Any]() )
        
        // Set companionAdViews to nil if there are no companionAdViews
        let playerDetails = IMAWrapperPlayerDetails(contentPlayer: otvPlayer,
                                                        adsUIView: playerView,
                                                          adTagURL: adTagURL,
                                                  companionAdViews: nil)
        
        // Initialise the IMAWrapper object
        imaWrapper = IMAWrapper(withPlayerDetails: playerDetails,
                                      withDelegate: self,
                                      withSettings: setupSettings)
        
        // Request ads to start, no need to call player.play() since the player will start once adverts complete
        // N.B. requestAds() will return false if the adTagURL hasn't been set/ is empty.
        if imaWrapper?.requestAds() == true {
          print("IMAWrapper: requestAds returned true")
        }
       }

6. Build and run on a device to see the adverts.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Playback statistics

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The CONNECT Player SDK provides classes and interfaces enabling you to access statistics concerning the player and rendering of playback.

    public class OTVPlaybackAnalytics: NSObject {
    	public var player: Player
    	public var rendering: Rendering
    }

Playback analytics related to adaptive streaming.  
C++

    public protocol Player {

      /// Returns the total amount of buffered content, in seconds.
      /// - Returns: the total amount of buffered content, in seconds
      func bufferedDuration() -> Double
      
      /// Returns an array of resoloutions available in the playlist
      /// - Returns: an array of resoloutions available in the playlist, or nil if unknown
      /// - Note: This function will make a seperate request for the Master playlist to return all the availableresoloutions. This request is only done once you call this function and only on the first time you call it for that contnet. Every subsiquent call on the same content does not requst the playlist again.
      func availableResoloutions() -> [CGSize]?
      
      /// Returns the resoloution of the content being played
      /// - Returns: the Resoloution of the content being played as a  CGSzie e.g CGSize(width: 1920, height: 1080) or CGSizeZero if Uknown or Audio only.
      func selectedResoloution() -> CGSize
      
      /// Returns the accumulated duration, in seconds, until the player item is ready to play.
      /// - Returns: the accumulated duration, in seconds, until the player item is ready to play or -1 if uknown
      func startUpTime() -> Double
      
      /// Returns the total number of playback stalls encountered.
      /// - Returns: total number of playback stalls encountered.
      func numberOfStalls() -> Int
      
      /// Returns the playback type can be live, VOD, or from a file. If nil is returned the playback type is unknown.
      /// - Returns: the playback type can be live, VOD, or from a file. If nil is returned the playback type is unknown.
      func playbackType() -> String?
      
      /// Returns the date and time at which playback began for this event.
      /// - Returns: the date and time at which playback began for this event.
      func playbackStartDate() -> Date?
      
      /// Returns the offset, in seconds, in the playlist where the last uninterrupted period of playback began.
      /// - Returns: the offset, in seconds, in the playlist where the last uninterrupted period of playback began.
      func playbackStartOffset() -> TimeInterval
      
    }

Playback analytics related to network usage.  
C++

    public protocol Rendering {
      
      /// Returns the total number of dropped video frames
      /// - Returns: the total number of dropped video frames
      func frameDrops() -> Int
      
      /// Returns the total number of dropped dived by the length of watched content
      /// - Returns: the total number of dropped dived by the length of watched content
      func frameDropsPerSecond() -> Int

      /// Returns the frame rate of the current content as measured during playback.
      /// - Returns: the frame rate of the current content as measured during playback.
      func framesPerSecond() -> Int
      
      /// Returns the frame rate of the current content as announced in the stream.
      /// - Returns: the frame rate of the current content as announced in the stream.
      /// - Note: This function will make a seperate request for the Master playlist to return all the stream information. This request is only done once you call this function and only on the first time you call it for that contnet. Every subsiquent call on the same content does not requst the playlist again.

      func framesPerSecondNominal() -> Int

    }

Notification and all event type.  
C++

    public static let OTVPlaybackAnalyticsNotification = Notification.Name("OTVPlaybackAnalyticsNotification")
    public enum Event: Int {
        case availableResoloutionChanged = 0
        case selectedResolutionChanged = 1
      }

Observe the OTVPlaybackAnalyticsNotification and get the event  
C++

    NotificationCenter.default.addObserver(self,
                                           selector: #selector(handlePlaybackAnalyticsNotification(notificaition:)),
                                           name: .OTVPlaybackAnalyticsNotification,
                                           object: nil)

    func handlePlaybackAnalyticsNotification(notificaition: NSNotification) {
        if let otvPlaybackNotificationType = notificaition.object as? OTVPlaybackAnalytics.Event {
          if (otvPlaybackNotificationType == .availableResoloutionChanged) {
            print("notificationTypeSelectedResolution AvailibleResoloution: ", player?.playbackAnalytics?.player.availableResoloutions() ?? [0,0])
          }
          else if (otvPlaybackNotificationType == .selectedResolutionChanged) {
            print("notificationTypeSelectedResolution SelectedResoloution: ", player?.playbackAnalytics?.player.selectedResoloution() ?? [0,0])
          }
        }
      }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Player lifecycle

To create your project and initialize the player instance, see [Creating the player](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/creating-the-player.md).

## Creating a player

There are several ways to create a player instance. You can:

* Create a player from a URL.

* Create a player from a player item initialized from a URL.

* Creating a player from a player item initialized from an `OTVAVURLAsset`.

* Create a player and set the player item

Click here to see the example code.  

### **Creating a player from URL**

    let assetURL = URL(string: "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_clear/index.m3u8")!
    let player = OTVAVPlayer(url: assetURL)

#### **Creating a player from player item initialized from URL**

    let assetURL = URL(string: "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_clear/index.m3u8")!
    let playerItem = OTVAVPlayerItem(url: assetURL)
    let player = OTVAVPlayer(playerItem: playerItem)

##### **Creating a player from player item initialized from OTVAVURLAsset**

    let assetURL = URL(string: "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_clear/index.m3u8")!
    let asset = OTVAVURLAsset(url: assetURL)
    let playerItem = OTVAVPlayerItem(asset: asset)
    let player = OTVAVPlayer(playerItem: playerItem)

##### **Creating a player and setting player item**

    let assetURL = URL(string: "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_clear/index.m3u8")!
    let asset = OTVAVURLAsset(url: assetURL)
    let playerItem = OTVAVPlayerItem(asset: asset)
    let player = OTVAVPlayer()
    player.replaceCurrentItem(with: playerItem)

## Attach player for Audio/Video rendering

### Attaching a player to UIView

Before playing a stream, the player must be attached to a `UIView` to get the audio and video rendered.
Click here to see the example code.  

#### **Creating a view with AVPlayerLayer to be attached to a player**

    class PlayerView: UIView {
      var player: AVPlayer? {
        get { return playerLayer.player }
        set { playerLayer.player = newValue }
      }
      var playerLayer: AVPlayerLayer {
        return layer as! AVPlayerLayer
      }
      override class var layerClass: AnyClass {
        return AVPlayerLayer.self
      }
    }

**Setting the player to the view**

    @IBOutlet weak var playerView: PlayerView!
    playerView.player = player

The following code can be added to indicate how the layer displays the video content within its bounds.

##### **Setting the video rendering mode in the view**

    playerView.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill

For more information, see the Apple document [AVFoundation/AVLayerVideoGravity](https://developer.apple.com/documentation/avfoundation/avcapturevideopreviewlayer/1386708-videogravity).  

|                                                   AVLayerVideoGravity                                                   |                                           Descriptions                                           |
|-------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
| [resizeAspect](https://developer.apple.com/documentation/avfoundation/avlayervideogravity/1387116-resizeaspect)         | The player should preserve the video's aspect ratio and fit the video within the layer's bounds. |
| [resizeAspectFill](https://developer.apple.com/documentation/avfoundation/avlayervideogravity/1385607-resizeaspectfill) | The player should preserve the video's aspect ratio and fill the layer's bounds.                 |
| [resize](https://developer.apple.com/documentation/avfoundation/avlayervideogravity/1387460-resize)                     | The video should be stretched to fill the layer's bounds.                                        |

### Attaching a player to SwiftUI View

The player must be attached to `VideoPlayer` view of SwiftUI to get the audio and video rendered during playback.
Click here to see the example code.  

#### **Attaching OTVAVPlayer to VideoPlayer View**

     VideoPlayer(player: otvAVPlayer)

## Playing a stream

Play a stream that has been set by calling:

    player.play() /* https://developer.apple.com/documentation/avfoundation/avplayer/1386726-play */

or

    player.rate = 1.0 /* https://developer.apple.com/documentation/avfoundation/avplayer/1388846-rate */

or

    player.playImmediately(atRate: 1.0) /* https://developer.apple.com/documentation/avfoundation/avplayer/1643480-playimmediately */

## Pausing a playback

Pause the playback by calling:

    player.pause() /* https://developer.apple.com/documentation/avfoundation/avplayer/1387895-pause */

or

    player.rate = 0.0 /* https://developer.apple.com/documentation/avfoundation/avplayer/1388846-rate */

## Seeking through a playback stream

You can seek through a playback stream.
Click here to see the example code.  

### **Seeking through media**

    // Sets the current playback time to the specified time and executes the specified block when the seek operation completes or is interrupted.
    func seek(to: CMTime, completionHandler: ((Bool) -> Void)?) /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1387418-seek */

    //Sets the current playback time within a specified time bound and invokes the specified block when the seek operation completes or is interrupted.
    func seek(to: CMTime, toleranceBefore: CMTime, toleranceAfter: CMTime, completionHandler: ((Bool) -> Void)?) /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1387753-seek */

    // Sets the current playback time to the time specified by the date object.
    func seek(to: Date, completionHandler: ((Bool) -> Void)?) -> Bool /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1389877-seek */

    // Cancels any pending seek requests and invokes the corresponding completion handlers if present.
    func cancelPendingSeeks() /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1388316-cancelpendingseeks */

#### **Accessing timing information**

    // Returns the current time of the item.
    func currentTime() -> CMTime /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1387230-currenttime */

    // Returns the current time of the item as an NSDate object.
    func currentDate() -> Date? /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1386188-currentdate */

    // The duration of the item.
    var duration: CMTime /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1389386-duration */

    // The timebase information for the item.
    var timebase: CMTimebase? /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1387605-timebase */

##### **Accessing timing information**

    // An array of time ranges indicating media data that is readily available.
    var loadedTimeRanges: [NSValue] /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1389953-loadedtimeranges */

    // An array of time ranges within which it is possible to seek.
    var seekableTimeRanges: [NSValue] /* https://developer.apple.com/documentation/avfoundation/avplayeritem/1386155-seekabletimeranges */

|                                 |                                                                                **VOD**                                                                                |                                                                                                               **LIVE**                                                                                                                |
|---------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `currentTime() -> CMTime`       | The time of the current playback position. The value starts from 0 to the `duration.`                                                                                 | The time of the current playback position. The value starts from the time near the end of the seekable window (three segments before the last one of the manifest) and keeps increasing. It should drop in the seekable range window. |
| `currentDate() -> Date?`        | The date and time of the current playback position. The value of the program date time from the manifest. E.g. #EXT-X-PROGRAM-DATE-TIME:2022-02-19T14:54:23.031+08:00 | The date and time of the current playback position. The value of the program date time from the manifest. E.g. #EXT-X-PROGRAM-DATE-TIME:2022-02-19T14:54:23.031+08:00                                                                 |
| `duration: CMTime`              | The duration of the content.                                                                                                                                          | The duration of all the segments in the manifest.                                                                                                                                                                                     |
| `seekableTimeRanges: [NSValue]` | The time range of the static seekable window, \[0, duration\].                                                                                                        | The time range of the dynamic seekable window, \[startTime, startTime + duration \]. The startTime is the current time of the first segment in the manifest, and the value keeps updating.                                            |

For more information, see the Apple document [Seeking Through Media](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/seeking_through_media).

## Observing the playback time

You can use the following code to observe the currentTime change and handle the UI about the time update in the closure.

    // You need to retain the instance timeObserverToken to make sure the closure can be called.
    timeObserverToken = player.addPeriodicTimeObserver(forInterval: time, queue: .main) {
    	[weak self] time in
        // update player UI for time update
    }
    // You also need to remove the observer when it's not used:
    player.removeTimeObserver(timeObserverToken)
    timeObserverToken = nil

For more information, see the Apple document [Observing the Playback Time](https://developer.apple.com/documentation/avfoundation/media_playback_and_selection/observing_the_playback_time).

## Changing streams

The playback stream can be changed by calling:

    let playerItem = OTVAVPlayerItem(url: assetURL)
    player.replaceCurrentItem(with: playerItem)

## Stopping playback

Playback can be stopped by calling:

    player.replaceCurrentItem(with: nil)

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Playing the download

Playback starts in the same way as for online playback but using the `offlineURL` instead. The client application picks an asset from the list of available assets. It retrieves the local (offline) URL associated with the download, loading and playing that URL just like a standard CDN-hosted stream.

    func getPlaybackAsset(title: String) -> OTVAVURLAsset? {
    	for persistenceAsset in OTVPersistenceManager.shared.getDownloads() {
    		if persistenceAsset.state == .downloaded && title == persistenceAsset.title {
    			if let streamURL = persistenceAsset.offlineURL {
    				return OTVAVURLAsset(url: persistenceAsset.offlineURL)
    			} 
    	}
    	// Cannot find the persisted asset
    	return nil
    }

## Playback while downloading

Only `OTVPersistenceAsset` assets in the `.downloaded` state have a valid `offlineURL` property for playback. However, providing the device is online, you can play assets in the process of downloading using the CDN-hosted URL.

**Next step:** You can [purge the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/purging-the-download.md).

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Purging the download

When a user deletes a downloaded asset on their device, the client application removes the downloaded asset in parallel or sequentially by calling `deleteDownload()`. This purge download operation is asynchronous, as removal is potentially time-consuming.

## Example code

The following code example shows how to purge a given downloaded/downloading `OTVPersistenceAsset` asset and its related licence.

    if true == self.otvPersistenceManager?.deleteDownload(asset: (asset)!) {
    NotificationCenter.default.post(name: .notificationDownloadRemoved,
    object: nil, userInfo: nil)
    }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# QuickMark forensic watermarking

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

The [Nexguard](http://www.nexguard.com/company/inquiry/) library forensic watermarking tool embeds a unique, invisible serial number onto video/audio content.

Version 2 of QuickMark watermarking can work in two different modes:

* In **pull mode**, the player acquires the watermark from the configured server as necessary.

* In **push mode**, the customer application acquires the watermark and provides it to the player for display.

The `OTVQuickmark` class abstracts the NexGuard-QuickMark library functionality that provides watermarking to the SDK.

QuickMark is available for both iOS and tvOS.

## Contents

The QuickMark framework contains libraries for iOS and tvOS. QuickMark also requires the Swift Protobuf framework, which is delivered alongside.

* All QuickMark and dependant frameworks are contained in the opy-ios-fps-\<version\>-quickmark.zip file, delivered in the iOS SDK pack.

* The QuickMark libraries are contained within the OTVQuickMarkView.xcframework.

* The Swift Protobuf frameworks are contained within the **SwiftProtobuf.xcframework.**

![Screenshot 2021-06-29 at 14.03.44.png](https://docs.nagra.vision/__attachments/a_1424d0484ba6bcc0db059fd4232c9c59979ff80dc19248e1f0b5f3b5631e4485/Screenshot%202021-06-29%20at%2014.03.44.png?cb=ac20b8a3704614d87fed7b6ca55282d3)

## Prerequisites

A watermarking server to generate the watermarked surface. Associated with this:

* Tenant Id - you may use `TEST_TENANT` during integration

* Service endpoint URL

* Secret key

* API Key to authenticate with the service endpoint if required (optional)

A system to generate 24-bit Watermark IDs (contained within the token) to identify the user, session, and device.

Swift Protobuf is needed for the OTVQuickmarkView sdk. The original Swift Protobuf library can be found at <https://github.com/apple/swift-protobuf>.

* Swift Protobuf gives multiple examples of how to include the library in your project. NAGRA recommends using version 1.6.0 as this is the version we have integrated and tested ourselves.

* A compatible version of the library is included with the SDK deliverable from NAGRA.

* The Swift Protobuf library is linked but not embedded inside **OTVQuickMarkView.framework** . You will need to put Swift Protbuf for your target as a dependency when linking **OTVQuickMarkView.framework**.

![Screenshot 2021-02-09 at 15.05.00.png](https://docs.nagra.vision/__attachments/a_08a48e9a2605b48e3d5c730451fd2b805d4973d90fbb0c108e2cd060d2a82667/Screenshot%202021-02-09%20at%2015.05.00.png?cb=7f78fb3aafe23fcab695af4ad78d98c3)

If added correctly, you should find the frameworks in the following Xcode build-phases section of the project settings **Link Binary with Libraries** and **Embed Frameworks**.

## Configuration Values

OTVQuickmark requires several configuration values for **push** and **pull mode**. The table below shows which values are necessary and optional for the two modes.  

|               |   **Required configuration values**    |                              **Optional configuration values**                               |
|---------------|----------------------------------------|----------------------------------------------------------------------------------------------|
| **Pull Mode** | * `token` * `url` * apiKey * `signKey` | * `computeSignatureCb` * `saveBlobCb` * `errorCb` * `messageCb`                              |
| **Push Mode** | * `signKey`                            | * `url` * `apiKey` * `token` * `computeSignatureCb` * `saveBlobCb` * `errorCb` * `messageCb` |

If you decide not to create your own callbacks, `OTVQuickMark` will use its own internal callbacks, as shown in the example code. The functionality of these callbacks are:

* `computeCallback`: This internal callback uses the `signKey` to compute the SHA-1 Signature

* `errorCallback`: This internal callback prints out the error message to the console.

* `messageCallback`: This internal callback prints out the message callback to the console, which contains the debug information

* `saveblobCallback`: This callback has no internal functionality.

## Pull mode example configuration

Instantiate and configure the `OTVWatermark` class providing all the configuration parameters.

      func setupOTVQuickmark() {
        do {
          try otvQuickmark = OTVQuickmark(token: nxgdQmSrvToken, apiKey: nxgdQmSrvApiKey, url: nxgdQmSrvUrl)
        }
        catch {}

Set the sign key with a specific method call.

        otvQuickmark?.setSignKey(signKey: nxgdQmsignKey)

The `PlayerView` and `AVPlayerLayer` must be bound together to allow the presentation of the watermark. Call `bind(playerView:playerLayer:)` passing references to your `PlayerView` and `AVPlayerLayer`.

        otvQuickmark?.bind(playerView: playerView, playerLayer: playerView.playerLayer)

When using `TEST_TENANT` as the tenant, you should see a stub pattern displayed over your video.  
![watermarked-video#1.png](https://docs.nagra.vision/__attachments/a_b950944edc1e1cfa8652e14b9cffbd03522f1b9363b291c0dfdb8c8941fdbe1a/watermarked-video%231.png?cb=6b29add09e8797b93f73db4612e9bfcb)

## Push mode example configuration

Push mode has additional requirements; two variants can provide them. The first (simpler) is where default callbacks are supplied within the **OTVQuickMarkView.framework**. The code snippets below describe the second variant where you need to implement callback methods.

Instantiate and configure the `OTVWatermark` class providing the names of your own callback methods.

      func setupOTVQuickmark() {
        do {
            try otvQuickmark = OTVQuickmark(computeSignatureCb: computeCb, saveBlobCb: saveblob, errorCb: errorCb, messageCb: messageCb)

The remaining configuration parameters need to be passed to your implementation that will do the work of acquiring the watermark image.

          quickMarkNetworkService = try QuickMarkSimpleNetworkService.init(url: nxgdQmSrvUrl,
                                                                           token: nxgdQmSrvToken,
                                                                           apiKey: nxgdQmSrvApiKey,
                                                                           onMessageCb: onNetworkMessageCb,
                                                                           onErrorCb: onNetworkErrorCb,
                                                                           onBlobReceivedCb: onNetworkblobreceived)

For brevity, the full example code of the callbacks and `QuickMarkSimpleNetworkService` class is only provided within the opy-sdk-ios-fps-\<version\>-example-code.zip file.

As in the **pull mode** example, the `setSignKey()` and `bind()` methods must be called.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Renewing a licence

The application needs to renew a licence, pre-emptively or already expired, to continue playback of the offline content.  
The **offline-license-renewal (** or *Offline License Renewal* of UnifiedExampleCode) example application demonstrates how to listen for a licence expiry error and consequently renew the licence.

The **drm-token-passing** (*DRM Token Passing* of UnifiedExampleCode) example application demonstrates decoding a token, which enables discovery of the licence duration.

Licence renewal can be split into two stages: [Expiry discovery](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/renewing-a-licence.md#Renewingalicence-Expirydiscovery) and [Renewal](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/renewing-a-licence.md#Renewingalicence-Renewal).

## Expiry discovery

There are two routes to finding out when a licence will expire and require renewal.

### Getting the licence duration

There is no generic API in Player SDK to return the licence expiry value as Apple does not expose it. How the licence expiry information is obtained depends on which FairPlay server the client is using.

The application can get the licence information by calling FairPlay server API or parsing the DRM data associated with the stream.

* If a MediaLive/SDP server is used as the FairPlay server, see the [Get license information from MediaLive Multidrm server](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/get-licence-information-from-medialive-multidrm-se.md) page for licence expiry.

* If using the SSP headend, the JWT token will have 'start', 'end' and 'duration' keys in the `contentsRights` dictionary. An example of how to obtain the `contentRights` is in the **drm-token-passing** example application.

* If not using the SSP headend, it is expected you already know how to obtain the licence duration.

With the duration, your application can determine when to request a licence renewal. This method allows an application to pre-emptively request one or more licences that are about to expire.

### Licence error notification containing an expired lease message

This requires setting a listener for the `OTVDRMLicenseError` notification. On receipt of the notification, if the notification object equals `.keyResponseWithExpiredLease`, your application must renew the licence to continue playing the content.

    NotificationCenter.default.addObserver(self, selector: #selector(self.licenseErrorNotification), name: .OTVDRMLicenseError,object: nil)

    @objc func licenseErrorNotification(notification: NSNotification) {
        let errorType = notification.object as? OTVDRMManager.OTVDRMLicenseError
        if errorType  == OTVDRMManager.OTVDRMLicenseError.keyResponseWithExpiredLease {
          if let info = notification.userInfo as? [String: Any]{
            keyIdentifier = info[OTVDRMManager.Keys.keyResponseWithExpiredLease] as? String ?? ""
            // Renew the license with the identifier
            )
          }
        }
      }

## Renewal

Two methods of licence renewal are available. While both have the same result, the `DRMManager` method may be used before you have a persistent asset and allow a form of pre-delivery, whereas the requirement for a persistent asset for the second method renders it reactive.

### OTVDRMManager

* Set up the license delegate. The `DRMMAnager` requires a licence delegate set up with a token.

* Call `renewLicense` on the `DRMManger`, passing the keyID and any required license options as parameters.

    if let sspLicenseDelegate = licenseDelegate as? OTVSSPLicenseDelegate, let offlineURL = self.opyPersistenceAsset?.offlineURL() {
        // The stream token is specific for OTVSSPLicenseDelegate, and it's different for each stream.
        // Make sure set the stream token to license delgate before each playback/download.
        sspLicenseDelegate.setStream(token: self.streamToken, with: offlineURL)
    }
    OTVDRMManager.shared.setLicenseDelegate(licenseDelegate)

    let options = [OTVPersistenceManager.Keys.OTVLicenseOptionOffline: true as Any]
    OTVDRMManager.shared.renewLicense(identifier: keyIdentifier, license: options)

### OTVPersistenceAsset

* Get the correct persistence asset.

* Set up the license delegate with the correct token.

* Call `persistenceAsset renewLicense` method, passing the `licenseDelegate`.

    var asset: OTVPersistenceAsset? = {
    	let downloads = OTVPersistenceManager.shared.getDownloads() 
    	for download in downloads {
    		let licenseDuration = download.licenseInfo?.duration
    		// Calculate and check if license gets expired, if yes return
    		return download
    	}
    	return nil
    }()
    if let sspLicenseDelegate = licenseDelegate as? OTVSSPLicenseDelegate, let offlineURL = self.opyPersistenceAsset?.offlineURL() {
        // The token is specific for OTVSSPLicenseDelegate, and it's specific for each stream.
        // Make sure set the stream token to license delgate before each renew license.
        sspLicenseDelegate.setStream(token: self.streamToken, with: offlineURL)
    }
    asset?.renewLicense(delegate: licenseDelegate)

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Resolution capping

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

This feature enables you to configure the CONNECT Player SDK to limit the maximum resolution users will experience during playback. HLS layers above the selected threshold will be dismissed and ignored by the adaptation algorithms.

## Example code

`OTVAVPlayer` has a public API that lets you configure this feature by specifying the width and height (in pixels) at which the cap will be applied. Zero values (default) for width and height will remove any resolution limit.

      let maxResolution = CGSize(width: 1280, height: 720)
      otvPlayer.currentItem?.preferredMaximumResolution = maxResolution

* If the maximum resolution provided is below the lowest available resolution in the stream, `OTVAVPlayer` will select the lowest possible resolution above the set limit.

* Changing the maximum resolution might not take effect immediately as the player may already have some content buffered and ready to play.

* HLS streams do not need to describe their layers' resolutions, in which case the cap is not applied.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Retrieving the licence

Set up your application to request licences from the licence server in a similar way as for encrypted streaming. In both cases, `OTVPersistenceManager` fetches the licence and persists it for the lifetime of the `OTVPersistenceAsset` it creates.

## Prefetch the licence before downloading

If the content identifier is known to the application, the application may request the licence before instigating the download; this is sometimes called the prefetch stage. When requesting a licence for a persistent asset, the App must request the licence from the `OTVDRMManager` with the licence option `OTVLicenseOptionOffline` set to true. An instance of `OTVLicenseDelegate` needs to be set on `drmmanager` before prefetching the licence. Make sure this prefetch method is used only for a single download at a time.

To prefetch licences for multiple downloads, use the following to ensure that there are separate delegates associated with each download:

    OTVPersistenceManager.shared.startDownload(urlAsset: urlAsset, title: assetName, licenseDelegate: delegate, artwork: nil, options: option) {
            }

### Request the licence while downloading

If the content identifier is not known and is embedded within the `EXT-X-KEY` or `EXT-X-SESSION-KEY` tag of the HLS playlists, the `OTVPersistenceManager` handles requesting the licence on behalf of the application.

## Example code

    if let contentId = stream.contentId, !contentId.isEmpty {
      let options = [OTVDRMManager.Keys.OTVLicenseOptionOffline: true as Any]
      OTVDRMManager.shared.requestLicense(identifier: contentId, license: options)
    }

**Next step:** For encrypted content, you can now [start the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/starting-the-download.md).

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Running the application

To run the app in clear playback mode:

1. Connect an Apple device.

2. Build and run the app on your device.

3. The build commences, and on successful completion, the stream should play on the device. The NAGRA logo will be displayed during playback to indicate that the integration variant is being used.

If you experience difficulties, check your application against the basic playback example application (iOS only). It may be useful to increase the logging verbosity of the SDK, which can be done with the following code:

    OTVSDK.setLogging(level: .debug)

**Next step:** You can now add more [Player features](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/adding-player-features.md) to your app.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Secure Session Management

The SDK supports NAGRA's Secure Session Manager (SSM), enabling monitoring and limiting the number of sessions in parallel, chiefly to protect against account sharing. Security is enhanced as the session manager is linked to the license manager, with the licence regularly renewed during playback.

When the player acquires or renews a licence for playback, it also needs to obtain a session token. The licence server provides the session tokens (per content or account), counts the number of active sessions, and limits the number of permitted concurrent sessions.

An SSM session is set up when a user starts playback of a content item and is torn down when playback stops. SSM can be run in App enforcement mode or DRM enforcement mode; if the SSM server does not receive a heartbeat at regular intervals, it deems the session expired, the licence is not renewed, and the session count drops by one. This ensures that even if no `teardown` message is sent by the player (for example, a device lost network connection), the session expires anyway.

The SDK provides two methods for SSM integration, called V1 or V2.

With V1 the client application or player will do the SSM setup separately before the playback/retrieving license, and do the SSM teardown when playback stops.

With V2 and the latest SSP license server v2 API, the SSM setup will be done during the license request and there is no SSM setup from client. The session will be torn down when zapping. Application can call SSM tear down if user doesn't do zap but need free the session and give to other devices.

Please refer the different page for the details

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Selecting the download stream

As described in [Starting the download](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/starting-the-download.md), in `options` you can set the:

* Download bitrate with the `AVAssetDownloadTaskMinimumRequiredMediaBitrateKey` key.

* Download resolution with the `AVAssetDownloadTaskMinimumRequiredPresentationSizeKey` key. (Only available on iOS 14 and above.)

However, this cannot guarantee the actual download bitrate or resolution will equal the expected value; see the Apple documents [AVAssetDownloadTaskMinimumRequiredMediaBitrateKey](https://developer.apple.com/documentation/avfoundation/avassetdownloadtaskminimumrequiredmediabitratekey) and [AVAssetDownloadTaskMinimumRequiredPresentationSizeKey](https://developer.apple.com/documentation/avfoundation/avassetdownloadtaskminimumrequiredpresentationsizekey). To address this issue, the Player SDK provides the `prepareDownload` API to return a preparing `OTVPersistenceAsset` instance, from which the available bitrates and resolutions can be retrieved when the state changes to `prepared`.

You will need to select one bitrate or resolution from the available bitrates array returned from `OTVPersistenceAsset.mediaInfo.availableStreamInfo`, and set the chosen value back to `OTVPersistenceAsset.mediaInfo.` `selectedStreamInfo`.

To set up DRM, an `OTVLicenseDelegate licenseDelegate` must be set by calling `OTVPersistenceAsset.setupFPS(with: licenseDelegate); `ignore this for download of clear content.

When the `OTVPersistenceAsset` instance has bitrate or resolution selected, and its DRM is set up, it can be passed to `startDownload` to start the downloading.

## Preparing for a download

    // Observe the OTVAssetDownloadStateChanged before calling `prepareDownload`
    NotificationCenter.default.addObserver(
          self,
          selector: #selector(self.stateChanged),
          name: .OTVAssetDownloadStateChanged,
          object: nil)

    let assetURL = "https://d3bqrzf9w11pn3.cloudfront.net/basic_hls_bbb_encrypted/index.m3u8"
    let assetName = "Big Buck Bunny"

    // prepare for a download
    // opyPersistenceAsset must be retained for later downloading
    let opyPersistenceAsset = OTVPersistenceManager.shared.prepareDownload(url: assetURL, title: assetName)

    // setup DRM for download
    let licenseDelegate = OTVSSPLicenseDelegate(certificateURL: certificateURL, licenseURL: licenseURL)
    licenseDelegate .setStream(token: token, with: assetURL)
    opyPersistenceAsset.setupFPS(with: licenseDelegate )

    // Handle state changed event
    @objc func stateChanged(notification: NSNotification) {
        let title = notification.userInfo![OTVPersistenceAsset.Keys.name] as! String
        let stateString = notification.userInfo![OTVPersistenceAsset.Keys.downloadState] as! String
    	guard let state = OTVDownloadState(rawValue: stateString) else {
    		return
    	}
    	swicth(state) {
    	case .prepared:
    		handlePreparedDownload()
    	...
    	}
    }

### Selecting a stream to download

    func handlePreparedDownload() {
    	let allStreamInfo = opyPersistenceAsset?.mediaInfo?.availableStreamInfo
    	var selectedStreamInfo: OTVStreamInfo?
    	for streamInfo in allStreamInfo  {
    		let bitrate = streamInfo.bitrate
    		let resolution = streamInfo.resolution
    		// find the bitrate or resolution you want to download
    		selectedStreamInfo = streamInfo
    		break
    	}
    	opyPersistenceAsset?.mediaInfo?.selectedStreamInfo = selectedStreamInfo
    	//Download all available tracks (audio/subtitle) for the asset to be downloaded. Use option .preferred to download only the preferred tracks. 
    	opyPersistenceAsset?.mediaSelections = .all
    	OTVPersistenceManager.shared.startDownload(asset: persistanceAsset, artwork: nil, options: nil)
    }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Server-side ad insertion

To test this feature and view the example code, please see the [Apple (FPS) SDK 5 Example Code Quick Start](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/apple-fps-sdk-5-example-code-quick-start.md) guide.

Server-side ad insertion is a method where advert setup is inserted into the OTT stream manifest during or after the encoding process in conjunction with an ad server.

The **serverside-ad-insertion** (or *Server side Ad Insertion* in UnifiedExampleCode) demonstrates how an application can:

* Extract HLS manifest (metadata) whenever a new HLS M3U8 file is present.

* Parse the metadata and look for tags and patterns that indicate when to insert the adverts.

This example only shows how to extract the advert information but does not perform the ad insertion, as there is no specific ad server against it. The application inserts advertisements into the `OTVAVPlayer `using metadata in the M3U8 using the metadata property on the `OTVAVPlayerItem` class.

## Prerequisites

A content server configured to inject advert tags into an HLS M3U8 manifest.

## Example code

The Swift source code contains the `VDGMetadataObserver` and the `VDGMetadataParser` classes that show how to parse the metadata to filter out and pass the custom tags. The following instructions show you how to create and attach the `OTVPlayer` and `OTVAVPlayerItem` to receive the metadata update. The following instructions show you how to create and attach the `OTVPlayer` and `OTVAVPlayerItem` to receive the metadata update.
Click here to view the example code.  

     // initialise the player by passing in the asset url.
        otvPlayer = OTVAVPlayer(url: assetURL)    

     //initialise the parser
        metadataParser = VDGMetadataParser()

    func setupMetadataObserver() {
        //set ViewController as delegate for metadataObserver protocol
        metadataParser.setMetadataObserver(self)

        //add observer to the playerItem's metadata property in order to be notified when it changes
        if let playerItem = otvPlayer.currentItem as? OTVAVPlayerItem {
          metadataObserver = playerItem.observe(\.metadata, options: [.new, .old]) { (_, change) in
              if let newMetadata = change.newValue {
                print(newMetadata)
                //parse received metadata for Ad tags
                self.metadataParser.parse(newMetadata)
             }
          }
        }
      }

Click here for an example of how to use the VDGMetadataObserver to create a protocol to listen to new metadata updates.  

    import Foundation
    /**
     `VDGMetadataObserver` is a protocol used by  `VDGMetadataParser` to update the metadata from player
    */
    protocol VDGMetadataObserver: NSObject {
      // If this metadata does not work, go back to original format and have parameter as a note
      /// Callback when receiving metedata update
      /// - Parameter periods: list of period which contains a XML fragment in following format:
      ///   '''
      ///   <Period id="AD_$period_sequence$" duration="PT$duration$S" start="PT$startTime$S">
      ///     <AssetIdentifier schemeIdUri="urn:com:vodafone:vtv:ssai:2019" value="ad"/>
      ///     <EventStream schemeIdUri="urn:scte:scte35:2013:xml" timescale="1000">
      ///       <Event id="$ad_sequence_number$" xmlns:VTV-SSAI="urn:vodafone:vtv:ssai:event">
      ///         <VTV-SSAI:beaconurls>
      ///           <TrackingEvents>
      ///             <Tracking event="$ad_event$">$http_beacon_UDL$</Tracking>
      ///           </TrackingEvents>
      ///         </VTV-SSAI:beaconurls>
      ///       </Event>
      ///     </EventStream>
      ///   </Period>
      ///   '''

      func newHLSAdUpdate(_ periods: [String])
    }

    /// Default implemetation of VDGMetadataObserver
    extension VDGMetadataObserver {
      
      func newHLSAdUpdate(_ periods: [String]) {
        //Do something here with the received data
        print(periods)
      }
    }

Click here for an example of how to use the VDGMetadataParser to parse the metadata received from OTVAVPlayerItem and abstract the adverts tags/custom tags from the M3U8 playlist.  

    import Foundation
    /**
     `VDGMetadataParser` is class used to parse playlist from player and call `VDGMetadataObserver` to expose the list of period
    */
    class VDGMetadataParser {
      
      weak var vdgMetaDataObserver:  VDGMetadataObserver?
      
      let hlsStreamInfoTag = "#EXTINF"
      let hlsDiscontinuityTag = "#EXT-X-DISCONTINUITY"
      let vdgSsaiBeaconTag = "#EXT-X-BEACON"
      let vdgSsaiIndentifier = "#EXT-X-BEACON:AD_LENGTH"
      
      let beaconAdLengthPrefix = ":AD_LENGTH"
      let beanconEventPrefix = ":EVENT"
      
      // The template used to generate the Event node
      // `1$` is the string of sequence number
      // `2$` is the string of event name
      // `3$` is the string of url
      let eventNodeTemplate = """
      \t\t<Event id="%1$@" xmlns:VTV-SSAI="urn:vodafone:vtv:ssai:event">
      \t\t\t<VTV-SSAI:beaconurls>
      \t\t\t\t<TrackingEvents>
      \t\t\t\t\t<Tracking event="%2$@">%3$@</Tracking>
      \t\t\t\t</TrackingEvents>
      \t\t\t</VTV-SSAI:beaconurls>
      \t\t</Event>
      """
      
      // The template used to generate the Period node
      // `1$` is the string of period id number
      // `2$` is the string of duration number in second
      // `3$` is the string of start time number in second
      // `4$` is the string of event node list
      let periodNodeTemplate = """
      <Period id="AD_%1$@" duration="PT%2$@S" start="PT%3$@S">
      \t<AssetIdentifier schemeIdUri="urn:com:vodafone:vtv:ssai:2019" value="ad"/>
      \t<EventStream schemeIdUri="urn:scte:scte35:2013:xml" timescale="1000">
      %4$@
      \t</EventStream>
      </Period>
      """
        
      func setMetadataObserver(_ observer: VDGMetadataObserver) {
        vdgMetaDataObserver = observer
      }

      func parse(_ metadata: String) {
        if !metadata.contains(vdgSsaiIndentifier) {
          print("Info: cannot find \(vdgSsaiIndentifier). L\(#line)")
          return
        }
        
        var periodNodes = [String](), periodSequence = 0
        var eventNodes = [String](), eventSequence = 0
        var periodStartTime = Float(0.0), periodDuration = Float(0.0)
        
        let lines = metadata.split { line in  line.isNewline }
        lines.forEach { line in
          let lineWithoutSpace = line.removeAllSpaces()
          switch self.getHlsTag(from: lineWithoutSpace) {
            case hlsStreamInfoTag: // #EXTINF
              let durationString = lineWithoutSpace.getSubString(after: hlsStreamInfoTag + ":", before: ",")
              if let durationNumber = Float(durationString) {
                periodDuration += durationNumber
              } else {
                print("Warning: wrong duration format. L\(#line)")
              }
            case hlsDiscontinuityTag: // #EXT-X-DISCONTINUITY
              print("Info: find \(hlsDiscontinuityTag). L\(#line)")
              if eventNodes.isEmpty {
                 print("Info: start of period. L\(#line)")
                 periodStartTime += periodDuration
                 periodDuration = 0
              } else {
                print("Info: end of period. L\(#line)")
                let events = eventNodes.joined(separator: "\n")
                let periodNode = self.createPeriodNode(sequence: periodSequence, duration: periodDuration, startTime: periodStartTime, events: events)
                periodNodes.append(periodNode)
                periodSequence += 1
                eventNodes.removeAll()
              }
            case vdgSsaiBeaconTag: // #EXT-X-BEACON
              let beancon = lineWithoutSpace.getSubString(after: vdgSsaiBeaconTag)
              if beancon.starts(with: beanconEventPrefix) {
                eventNodes.append(self.createEventNode(from: lineWithoutSpace, sequence: eventSequence))
                eventSequence += 1
                print("Info: create a new ad event. L\(#line)")
              } else if beancon.starts(with: beaconAdLengthPrefix) {
                eventSequence = 0
                print("Info: end of ad event. L\(#line)")
              }
            default:
              print("Info: Ignore this line: \(lineWithoutSpace). L\(#line)")
          }
        }
        vdgMetaDataObserver?.newHLSAdUpdate(periodNodes)
      }

      // Helper method to create a event node from input string by using the teamplate
      private func createEventNode(from string: String, sequence: Int) -> String {
        let name = string.getSubString(after: "EVENT=", before: ",URL")
        let url = string.getSubString(after: "URL=")
        return String(format: eventNodeTemplate, arguments: [String(sequence), name, url])
      }
      
      // Helper method to create a period node from input string by using the teamplate
      private func createPeriodNode(sequence: Int, duration: Float, startTime: Float, events: String) -> String {
        return String(format: periodNodeTemplate, arguments: [String(sequence), String(duration), String(startTime), events])
      }
      
      // Helper method to get HLS tag from one line of playlist
      private func getHlsTag(from string: String) -> String {
        let tag = string.getSubString(before: ":")
        if tag.isEmpty {
          return string
        } else {
          return tag
        }
      }
    }

    fileprivate extension String {
      // Helper method to get substring from a string located after and before a specific string
      func getSubString(after: String, before: String = "") -> String {
        if let startIndex = self.range(of: after)?.upperBound,
          let endIndex = before.isEmpty ? self.endIndex : self.range(of: before)?.lowerBound {
          return String(self[startIndex..<endIndex])
        } else {
          return ""
        }
      }
      
      // Helper method to get substring from a string located before a specific string
      func getSubString(before: String) -> String {
        if let endIndex = self.range(of: before)?.lowerBound {
          return String(self[startIndex..<endIndex])
        } else {
          return ""
        }
      }
    }

    fileprivate extension Substring {
      // Helper method to remove all space from the string
      func removeAllSpaces() -> String {
        return self.replacingOccurrences(of: " ", with: "")
      }
    }

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# Simulator support

The Player SDK is built with simulator support to enable you to build and test against an Xcode simulator. When doing so, create a FAT framework (iOS and simulator in one library). This sometimes means when building, you may need to remove arm64 support when building for a simulator.

The Player xcframework SDK contains the libraries for different targets; for example, iOS, iOS Simulator, tvOS and tvOS Simulator. The application can directly include the xcframework file and build for the Simulator target.

## Excluding simulator support

To exclude the arm64 libraries when building for a simulator:

1. Go to **Build Settings** and locate **Excluded Architectures**.

2. Select **Any iOS Simulator SDK** for both Debug and Release and set them to exclude **arm64** .

   ![Screenshot 2021-08-19 at 11.29.04.png](https://docs.nagra.vision/__attachments/a_a7c73639e62754267bc2aa9479fbebe7a362343c5a7f285d31924d232172effa/Screenshot%202021-08-19%20at%2011.29.04.png?cb=fc1d13ea4d58a62cf1d17d02db1b938c)
3. You should also set the build flag **Validate workspace** to **Yes** as shown below.

   ![image2021-8-20_12-48-51.png](https://docs.nagra.vision/__attachments/a_4f07d7d74779457807d9017dbe366bc8fdb968647719a7aa1fd65225225cbbb6/image2021-8-20_12-48-51.png?cb=6b6fac4c97e99f2ec2aaf5246950db55)
4. Repeat the process for tvOS but select **Any tvOS Simulator SDK** for both Debug and Release.

---
version: "5.14.x"
variant: "Default"
language: "en"
---
# SRT subtitle tracks

The CONNECT Player SDK supports external SRT subtitle tracks in a stream.

## Prerequisites

An external SRT subtitle track URL to be downloaded and used.

## Example code

After basic playback and track selection has been set up, you can add an external SRT track. The SRT track should be called before playback starts to be populated at the beginning of playback. However, if the track is not unique, it will not be added to the tracks list; for example, a track with the same language is already present.

To do this, you need an SRT url and call the method on `OTVAVPlayer`, `addSubtitleWithUrl(subtitleURL: String, mimeType: String, language: String)`.

* Parameter subtitleURL: The URL of the subtitle.

* Parameter mimeType: The mimeType of the subtitle, for SRT this should be `application/x-subrip`.

* Parameter language: The language of the subtitle, for example, `English`.

    _ = otvPlayer.addSubtitleWithUrl(subtitleURL: "URLOfSubtitle", mimeType: "application/x-subrip", language: "English")

As long as you conform to `OTVTracksChangedListener` as described in the [Track selection](https://docs.nagra.vision/connect-player-sdk-5-for-apple-fps-docs/5.14.x/Default/track-selection.md) feature, the delegate method `tracksChanged()` will be triggered when the tracks are downloaded and parsed by `OTVAVPlayer`.

[Next Page](https://docs.nagra.vision/llms-full.txt/1)
