diff --git a/.gitignore b/.gitignore index 9275855db..506f9ae93 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ ios/custom/Frameworks/tvos/*.xcframework _site/ Gemfile.lock vendor +.bundle diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c766605..2dbcca6eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [8.5.0] - 24-10-21 + +### Added + +- Added `SdkVersion` interface to be used by the sdk and its connectors to report version info. +- Added the `THEOads` SGAI ad integration for Android. See [THEOads](https://www.theoplayer.com/product/theoads) for more details. + +### Fixed + +- Fixed an issue on Web where all text tracks other than the selected would be set to `disabled` when enabling a text track. +- Fixed an issue on iOS where the player would crash when changing the presentationMode from fullscreen to picture-in-picture during ad playout. + +### Changed + +- On iOS, after closing picture-in-picture the player returns back to the previous presentationMode instead of always returning to inline. + ## [8.4.0] - 24-10-10 ### Added - Added a `posterStyle` property on `THEOplayerView` to allow overriding the default 16:9 poster style. +- Added a `theoads` property on `PlayerConfiguration.ads` to allow play-out of THEOads sources on Web platforms. +- Added a `hlsDateRange` property on `SourceDescription` to enable parsing and exposing date ranges from HLS playlists. This flag was already available on `PlayerConfiguration`, which applies for all HLS sources. ### Fixed diff --git a/android/build.gradle b/android/build.gradle index 207daedc2..f6687b9e7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,7 +28,8 @@ static def versionString(version) { // Extensions def enabledGoogleIMA = safeExtGet("THEOplayer_extensionGoogleIMA", 'false').toBoolean() def enabledGoogleDAI = safeExtGet("THEOplayer_extensionGoogleDAI", 'false').toBoolean() -def enabledAds = enabledGoogleIMA || enabledGoogleDAI +def enabledTHEOads = safeExtGet("THEOplayer_extensionTHEOads", 'false').toBoolean() +def enabledAds = enabledGoogleIMA || enabledGoogleDAI || enabledTHEOads def enabledCast = safeExtGet("THEOplayer_extensionCast", 'false').toBoolean() def enabledMediaSession = safeExtGet("THEOplayer_extensionMediaSession", 'true').toBoolean() @@ -61,6 +62,7 @@ android { // Extension buildConfig fields buildConfigField "boolean", "EXTENSION_GOOGLE_IMA", "${enabledGoogleIMA}" buildConfigField "boolean", "EXTENSION_GOOGLE_DAI", "${enabledGoogleDAI}" + buildConfigField "boolean", "EXTENSION_THEOADS", "${enabledTHEOads}" buildConfigField "boolean", "EXTENSION_ADS", "${enabledAds}" buildConfigField "boolean", "EXTENSION_CAST", "${enabledCast}" buildConfigField "boolean", "EXTENSION_MEDIASESSION", "${enabledMediaSession}" @@ -139,6 +141,14 @@ dependencies { compileOnly "com.theoplayer.theoplayer-sdk-android:integration-ads-dai:${theoplayer_sdk_version}" } + if (enabledTHEOads) { + println('Enable THEOplayer THEOads extension.') + implementation "com.theoplayer.theoplayer-sdk-android:integration-ads-theoads:${theoplayer_sdk_version}" + } else { + println('Disable THEOplayer THEOads extension.') + compileOnly "com.theoplayer.theoplayer-sdk-android:integration-ads-theoads:${theoplayer_sdk_version}" + } + if (enabledCast) { println('Enable THEOplayer cast extension.') implementation "com.theoplayer.theoplayer-sdk-android:integration-cast:${theoplayer_sdk_version}" @@ -153,6 +163,7 @@ configurations.configureEach { resolutionStrategy { force "com.theoplayer.theoplayer-sdk-android:integration-ads-ima:${theoplayer_sdk_version}" force "com.theoplayer.theoplayer-sdk-android:integration-ads-dai:${theoplayer_sdk_version}" + force "com.theoplayer.theoplayer-sdk-android:integration-ads-theoads:${theoplayer_sdk_version}" } } diff --git a/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt b/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt index 7f71e47da..6863c6d9d 100644 --- a/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +++ b/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt @@ -18,6 +18,8 @@ import com.theoplayer.android.api.ads.dai.GoogleDaiIntegration import com.theoplayer.android.api.ads.dai.GoogleDaiIntegrationFactory import com.theoplayer.android.api.ads.ima.GoogleImaIntegration import com.theoplayer.android.api.ads.ima.GoogleImaIntegrationFactory +import com.theoplayer.android.api.ads.theoads.TheoAdsIntegration +import com.theoplayer.android.api.ads.theoads.TheoAdsIntegrationFactory import com.theoplayer.android.api.cast.CastIntegration import com.theoplayer.android.api.cast.CastIntegrationFactory import com.theoplayer.android.api.event.EventListener @@ -80,6 +82,7 @@ class ReactTHEOplayerContext private constructor( var daiIntegration: GoogleDaiIntegration? = null var imaIntegration: GoogleImaIntegration? = null + private var theoAdsIntegration: TheoAdsIntegration? = null var castIntegration: CastIntegration? = null var wasPlayingOnHostPause: Boolean = false private var isHostPaused: Boolean = false @@ -295,6 +298,17 @@ class ReactTHEOplayerContext private constructor( } catch (e: Exception) { Log.w(TAG, "Failed to configure Google DAI integration ${e.message}") } + try { + if (BuildConfig.EXTENSION_THEOADS) { + theoAdsIntegration = TheoAdsIntegrationFactory.createTheoAdsIntegration( + playerView + ).also { + playerView.player.addIntegration(it) + } + } + } catch (e: Exception) { + Log.w(TAG, "Failed to configure THEOAds integration ${e.message}") + } try { if (BuildConfig.EXTENSION_CAST) { castIntegration = CastIntegrationFactory.createCastIntegration( diff --git a/android/src/main/java/com/theoplayer/source/SourceAdapter.kt b/android/src/main/java/com/theoplayer/source/SourceAdapter.kt index a037d8cbd..2dde5d0c2 100644 --- a/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +++ b/android/src/main/java/com/theoplayer/source/SourceAdapter.kt @@ -3,6 +3,7 @@ package com.theoplayer.source import android.text.TextUtils import android.util.Log import com.google.gson.Gson +import com.theoplayer.android.api.ads.theoads.TheoAdDescription import com.theoplayer.android.api.error.THEOplayerException import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableMap @@ -17,6 +18,7 @@ import com.theoplayer.android.api.source.addescription.GoogleImaAdDescription import com.theoplayer.android.api.player.track.texttrack.TextTrackKind import com.theoplayer.android.api.source.metadata.ChromecastMetadataImage import com.theoplayer.BuildConfig +import com.theoplayer.android.api.ads.theoads.TheoAdsLayoutOverride import com.theoplayer.android.api.error.ErrorCode import com.theoplayer.android.api.source.AdIntegration import com.theoplayer.android.api.source.dash.DashPlaybackConfiguration @@ -52,7 +54,16 @@ private const val PROP_ADS = "ads" private const val PROP_DASH = "dash" private const val PROP_DASH_IGNORE_AVAILABILITYWINDOW = "ignoreAvailabilityWindow" private const val PROP_HEADERS = "headers" +private const val PROP_AD_TAG_PARAMETERS = "adTagParameters" +private const val PROP_BACKDROP_DOUBLE_BOX = "backdropDoubleBox" +private const val PROP_BACKDROP_LSHAPE = "backdropLShape" +private const val PROP_CUSTOM_ASSET_KEY = "customAssetKey" +private const val PROP_OVERRIDE_LAYOUT = "overrideLayout" +private const val PROP_NETWORK_CODE = "networkCode" +private const val PROP_USE_ID3 = "useId3" + private const val ERROR_IMA_NOT_ENABLED = "Google IMA support not enabled." +private const val ERROR_THEOADS_NOT_ENABLED = "THEOads support not enabled." private const val ERROR_UNSUPPORTED_CSAI_INTEGRATION = "Unsupported CSAI integration" private const val ERROR_MISSING_CSAI_INTEGRATION = "Missing CSAI integration" @@ -252,6 +263,9 @@ class SourceAdapter { AdIntegration.GOOGLE_IMA.adIntegration -> parseImaAdFromJS( jsonAdDescription ) + AdIntegration.THEO_ADS.adIntegration -> parseTheoAdFromJS( + jsonAdDescription + ) else -> { throw THEOplayerException( ErrorCode.AD_ERROR, @@ -285,6 +299,40 @@ class SourceAdapter { .build() } + @Throws(JSONException::class) + private fun parseTheoAdFromJS(jsonAdDescription: JSONObject): TheoAdDescription { + if (!BuildConfig.EXTENSION_THEOADS) { + throw THEOplayerException(ErrorCode.AD_ERROR, ERROR_THEOADS_NOT_ENABLED) + } + return TheoAdDescription( + adTagParameters = parseAdTagParameters(jsonAdDescription.optJSONObject(PROP_AD_TAG_PARAMETERS)), + backdropDoubleBox = jsonAdDescription.optString(PROP_BACKDROP_DOUBLE_BOX), + backdropLShape = jsonAdDescription.optString(PROP_BACKDROP_LSHAPE), + customAssetKey = jsonAdDescription.optString(PROP_CUSTOM_ASSET_KEY), + networkCode = jsonAdDescription.optString(PROP_NETWORK_CODE), + overrideLayout = parseOverrideLayout(jsonAdDescription.optString(PROP_OVERRIDE_LAYOUT)), + useId3 = jsonAdDescription.optBoolean(PROP_USE_ID3), + ) + } + + private fun parseAdTagParameters(params: JSONObject?): Map = + params?.keys()?.asSequence() + ?.mapNotNull { key -> key to params.optString(key) } + ?.toMap() ?: emptyMap() + + private fun parseOverrideLayout(override: String?): TheoAdsLayoutOverride? { + if (override == null) { + return null + } + when (override) { + "single" -> return TheoAdsLayoutOverride.SINGLE + "single-if-mobile" -> return TheoAdsLayoutOverride.SINGLE + "l-shape" -> return TheoAdsLayoutOverride.LSHAPE + "double" -> return TheoAdsLayoutOverride.DOUBLE + } + return null + } + @Throws(JSONException::class) private fun parseTextTrackFromJS(jsonTextTrack: JSONObject): TextTrackDescription { val builder = TextTrackDescription.Builder(jsonTextTrack.optString(PROP_SRC)) diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 7fd6e611e..d361a7945 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -61,6 +61,7 @@ THEOplayer_logMediaSessionEvents = true THEOplayer_extensionGoogleIMA = true THEOplayer_extensionGoogleDAI = true THEOplayer_extensionCast = true +THEOplayer_extensionTHEOads = true THEOplayer_extensionMediaSession = true # Optionally limit timeUpdate rate, which could improve performance. diff --git a/example/package-lock.json b/example/package-lock.json index a7639eccf..a2fc9b894 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -5815,21 +5815,6 @@ "node": ">= 0.8" } }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", @@ -6379,9 +6364,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "engines": { "node": ">= 0.6" @@ -7813,9 +7798,9 @@ } }, "node_modules/express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -7823,14 +7808,14 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -7839,11 +7824,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -7873,13 +7858,13 @@ } }, "node_modules/express/node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -7890,27 +7875,6 @@ "node": ">= 0.8" } }, - "node_modules/express/node_modules/finalhandler/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -7949,45 +7913,6 @@ } ] }, - "node_modules/express/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/express/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -12221,12 +12146,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -13213,9 +13138,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13370,19 +13295,27 @@ "dev": true }, "node_modules/serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", diff --git a/example/src/custom/sources.json b/example/src/custom/sources.json index c6ed544f0..1855c2e12 100644 --- a/example/src/custom/sources.json +++ b/example/src/custom/sources.json @@ -436,5 +436,25 @@ "src": "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3" } } + }, + { + "name": "HLS - THEOads", + "os": ["ios", "web", "android"], + "source": { + "sources": { + "src": "https://cluster.dev.theostream.live/nfl-unified-channel/hls/k8s/live/scte35.isml/.m3u8", + "type": "application/x-mpegurl", + "hlsDateRange": true + }, + "ads": [ + { + "integration": "theoads", + "networkCode": "51636543", + "customAssetKey": "nfl-sgai-demo", + "backdropDoubleBox": "https://demo.theoads.live/img/THEOads_double_box.svg", + "backdropLShape": "https://demo.theoads.live/img/THEOads_L_Shape.svg" + } + ] + } } ] diff --git a/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift b/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift index efe33f0e1..7b2cd94d9 100644 --- a/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift +++ b/ios/presentationMode/THEOplayerRCTPresentationModeManager.swift @@ -9,8 +9,9 @@ public class THEOplayerRCTPresentationModeManager { private weak var player: THEOplayer? private weak var view: UIView? var presentationModeContext = THEOplayerRCTPresentationModeContext() - var presentationMode: THEOplayerSDK.PresentationMode = .inline - + private var presentationMode: THEOplayerSDK.PresentationMode = .inline + private var rnInlineMode: THEOplayerSDK.PresentationMode = .inline // while native player is inline, RN player can be inline or fullsceen + private var containerView: UIView? // view containing the playerView and it's siblings (e.g. UI) private var inlineParentView: UIView? // target view for inline representation private var movingChildVCs: [UIViewController] = [] // list of playerView's child VCs that need to be reparented while moving the playerView @@ -79,6 +80,7 @@ public class THEOplayerRCTPresentationModeManager { self.storeMovingVCs(for: containerView) self.moveView(containerView, to: fullscreenParentView, with: self.movingChildVCs) } + self.rnInlineMode = .fullscreen } private func exitFullscreen() { @@ -87,11 +89,12 @@ public class THEOplayerRCTPresentationModeManager { self.moveView(containerView, to: inlineParentView, with: self.movingChildVCs) self.clearMovingVCs() } + self.rnInlineMode = .inline } + + func setPresentationModeFromRN(newPresentationMode: THEOplayerSDK.PresentationMode) { + guard newPresentationMode != self.presentationMode else { return } - func setPresentationMode(newPresentationMode: THEOplayerSDK.PresentationMode) { - guard newPresentationMode != self.presentationMode, let player = self.player else { return } - // store old presentationMode let oldPresentationMode = self.presentationMode @@ -102,40 +105,58 @@ public class THEOplayerRCTPresentationModeManager { switch oldPresentationMode { case .fullscreen: if newPresentationMode == .inline { - // get out of fullscreen via view reparenting - self.exitFullscreen(); + self.exitFullscreen(); // stay inline on Native player, go to inline layout on RN } else if newPresentationMode == .pictureInPicture { - // get out of fullscreen via view reparenting - self.exitFullscreen(); - // get into pip - player.presentationMode = .pictureInPicture + self.setNativePresentationMode(.pictureInPicture) // go pip on Native player, keep fullscreen layout on RN } case .inline: if newPresentationMode == .fullscreen { - // get into fullscreen via view reparenting - self.enterFullscreen(); + self.enterFullscreen(); // stay inline on Native player, go to fullscreen layout on RN } else if newPresentationMode == .pictureInPicture { - // get into pip - player.presentationMode = .pictureInPicture + self.setNativePresentationMode(.pictureInPicture) // go pip on Native player, keep inline layout on RN } case .pictureInPicture: - if newPresentationMode == .fullscreen { - // get out of pip - player.presentationMode = .inline - // get into fullscreen via view reparenting - self.enterFullscreen(); - } else if newPresentationMode == .inline { - // get into pip - player.presentationMode = .inline + self.setNativePresentationMode(.inline) // always go inline on Native player, + if newPresentationMode == .inline { + if self.rnInlineMode == .fullscreen { + self.exitFullscreen() // and if required, switch to inline layout on RN, + } + } else if newPresentationMode == .fullscreen { + if self.rnInlineMode == .inline { + self.enterFullscreen() // and if required, switch to fullscreen layout on RN, } + } default: break; } // notify the presentationMode change - self.notifyPresentationModeChange(oldPresentationMode: oldPresentationMode, newPresentationMode: newPresentationMode) + self.notifyPresentationModeChange(oldPresentationMode: oldPresentationMode, newPresentationMode: self.presentationMode) } + + func setPresentationModeFromNative(newPresentationMode: THEOplayerSDK.PresentationMode) { + guard newPresentationMode != self.presentationMode else { return } + + // store old presentationMode + let oldPresentationMode = self.presentationMode + // set new presentationMode + self.presentationMode = newPresentationMode + + // adjust presentationMode to RN layout + if newPresentationMode == .inline { + self.presentationMode = self.rnInlineMode + } + + // notify the presentationMode change + self.notifyPresentationModeChange(oldPresentationMode: oldPresentationMode, newPresentationMode: self.presentationMode) + } + + private func setNativePresentationMode(_ presentationMode: THEOplayerSDK.PresentationMode) { + guard let player = self.player else { return } + player.presentationMode = presentationMode + } + private func notifyPresentationModeChange(oldPresentationMode: THEOplayerSDK.PresentationMode, newPresentationMode: THEOplayerSDK.PresentationMode) { // update the current presentationMode self.presentationMode = newPresentationMode @@ -155,7 +176,7 @@ public class THEOplayerRCTPresentationModeManager { self.presentationModeChangeListener = player.addEventListener(type: PlayerEventTypes.PRESENTATION_MODE_CHANGE) { [weak self] event in if let welf = self { if DEBUG_THEOPLAYER_EVENTS || true { PrintUtils.printLog(logText: "[NATIVE] Received PRESENTATION_MODE_CHANGE event from THEOplayer (to \(event.presentationMode._rawValue))") } - welf.setPresentationMode(newPresentationMode: event.presentationMode) + welf.setPresentationModeFromNative(newPresentationMode: event.presentationMode) } } if DEBUG_EVENTHANDLER { PrintUtils.printLog(logText: "[NATIVE] PresentationModeChange listener attached to THEOplayer") } diff --git a/ios/presentationMode/THEOplayerRCTView+PresentationMode.swift b/ios/presentationMode/THEOplayerRCTView+PresentationMode.swift index d208f983e..c48b01d9c 100644 --- a/ios/presentationMode/THEOplayerRCTView+PresentationMode.swift +++ b/ios/presentationMode/THEOplayerRCTView+PresentationMode.swift @@ -6,6 +6,6 @@ import THEOplayerSDK extension THEOplayerRCTView { func setPresentationMode(newPresentationMode: THEOplayerSDK.PresentationMode) { - self.presentationModeManager.setPresentationMode(newPresentationMode: newPresentationMode) + self.presentationModeManager.setPresentationModeFromRN(newPresentationMode: newPresentationMode) } } diff --git a/package-lock.json b/package-lock.json index 1709e9a00..043b7b27c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "react-native-theoplayer", - "version": "8.4.0", + "version": "8.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-native-theoplayer", - "version": "8.4.0", + "version": "8.5.0", "license": "SEE LICENSE AT https://www.theoplayer.com/terms", "dependencies": { "buffer": "^6.0.3" diff --git a/package.json b/package.json index 97d4a242f..5036b1c55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-theoplayer", - "version": "8.4.0", + "version": "8.5.0", "description": "A THEOplayer video component for react-native.", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/src/api/ads/AdsConfiguration.ts b/src/api/ads/AdsConfiguration.ts index 3480d8fe0..16974d169 100644 --- a/src/api/ads/AdsConfiguration.ts +++ b/src/api/ads/AdsConfiguration.ts @@ -52,6 +52,20 @@ export interface AdsConfiguration { *
- This is only available to define IMA Settings on iOS and/or Android. */ ima?: GoogleImaConfiguration; + + /** + * Whether to enable THEOads support. + * + * @since React Native THEOplayer SDK v8.4.0. + * @since Native THEOplayer SDK v8.2.0. + * + * @remarks + *
- This must be set to `true` in order to schedule a {@link TheoAdDescription}. + *
- This only applies to Web platforms. + * + * @defaultValue `false` + */ + theoads?: boolean; } /** diff --git a/src/api/source/SourceDescription.ts b/src/api/source/SourceDescription.ts index 5708a63ac..be48f78f1 100644 --- a/src/api/source/SourceDescription.ts +++ b/src/api/source/SourceDescription.ts @@ -92,6 +92,13 @@ export interface SourceConfiguration { */ timeServer?: string; + /** + * Whether the player should parse and expose date ranges from HLS playlists. + * + * @defaultValue `false` + */ + hlsDateRange?: boolean; + /** * Describes the metadata of a source. * diff --git a/src/api/version/SdkVersions.ts b/src/api/version/SdkVersions.ts new file mode 100644 index 000000000..801d174c6 --- /dev/null +++ b/src/api/version/SdkVersions.ts @@ -0,0 +1,15 @@ +export interface SdkVersions { + /** + * The version of the react-native SDK. + * + * @public + */ + readonly rn: string; + + /** + * The version of the native SDK dependency, if that applies. + * + * @public + */ + readonly native?: string; +} diff --git a/src/internal/adapter/THEOplayerWebAdapter.ts b/src/internal/adapter/THEOplayerWebAdapter.ts index c264eaee2..d848a507e 100644 --- a/src/internal/adapter/THEOplayerWebAdapter.ts +++ b/src/internal/adapter/THEOplayerWebAdapter.ts @@ -212,7 +212,11 @@ export class THEOplayerWebAdapter extends DefaultEventDispatcher // Apply native selection this._player.textTracks.forEach((textTrack: NativeTextTrack) => { - textTrack.mode = textTrack.uid === trackUid ? 'showing' : 'disabled'; + if (textTrack.uid === trackUid) { + textTrack.mode = 'showing'; + } else if (textTrack.mode === 'showing') { + textTrack.mode = 'disabled'; + } }); } }