diff --git a/CHANGELOG.md b/CHANGELOG.md index 6137ef23..072203d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - `LiveConfig.minTimeshiftBufferDepth` to control the minimum buffer depth of a stream needed to enable time shifting. - `Player.buffer` to control buffer preferences and to query the current buffer state +- `DownloadFinishedEvent` to signal when the download of specific content has finished ## [0.13.0] (2023-10-20) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt index 33e5d1a3..9f26e323 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt @@ -51,6 +51,7 @@ private val EVENT_CLASS_TO_REACT_NATIVE_NAME_MAPPING = mapOf( SourceEvent.AudioTrackAdded::class to "audioAdded", SourceEvent.AudioTrackChanged::class to "audioChanged", SourceEvent.AudioTrackRemoved::class to "audioRemoved", + SourceEvent.DownloadFinished::class to "downloadFinished", PlayerEvent.AdBreakFinished::class to "adBreakFinished", PlayerEvent.AdBreakStarted::class to "adBreakStarted", PlayerEvent.AdClicked::class to "adClicked", diff --git a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt index 05f17515..7afdadef 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt @@ -93,6 +93,7 @@ class RNPlayerViewManager(private val context: ReactApplicationContext) : Simple "subtitleAdded" to "onSubtitleAdded", "subtitleChanged" to "onSubtitleChanged", "subtitleRemoved" to "onSubtitleRemoved", + "downloadFinished" to "onDownloadFinished", "pictureInPictureAvailabilityChanged" to "onPictureInPictureAvailabilityChanged", "pictureInPictureEnter" to "onPictureInPictureEnter", "pictureInPictureExit" to "onPictureInPictureExit", diff --git a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt index 17775248..187dbf8c 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt @@ -567,6 +567,18 @@ class JsonConverter { json.putMap("newSubtitleTrack", fromSubtitleTrack(event.newSubtitleTrack)) } + is SourceEvent.DownloadFinished -> { + json.putDouble("downloadTime", event.downloadTime) + json.putString("requestType", event.downloadType.toString()) + json.putInt("httpStatus", event.httpStatus) + json.putBoolean("isSuccess", event.isSuccess) + event.lastRedirectLocation?.let { + json.putString("lastRedirectLocation", it) + } + json.putDouble("size", event.size.toDouble()) + json.putString("url", event.url) + } + else -> { // Event is not supported yet or does not have any additional data } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/extensions/Events.kt b/android/src/main/java/com/bitmovin/player/reactnative/extensions/Events.kt index 40cd6e47..b3a6ffb9 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/extensions/Events.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/extensions/Events.kt @@ -23,5 +23,6 @@ fun SourceEvent.getName(): String = when (this) { is SourceEvent.SubtitleTrackAdded -> "onSubtitleAdded" is SourceEvent.SubtitleTrackChanged -> "onSubtitleChanged" is SourceEvent.SubtitleTrackRemoved -> "onSubtitleRemoved" + is SourceEvent.DownloadFinished -> "onDownloadFinished" else -> "onSource${this.javaClass.simpleName}" } diff --git a/ios/Event+JSON.swift b/ios/Event+JSON.swift index 7230c2e7..7f4391aa 100644 --- a/ios/Event+JSON.swift +++ b/ios/Event+JSON.swift @@ -361,3 +361,22 @@ extension CastWaitingForDeviceEvent { } } #endif + +extension DownloadFinishedEvent { + func toJSON() -> [AnyHashable: Any] { + var json: [AnyHashable: Any] = [ + "name": name, + "timestamp": timestamp, + "downloadTime": downloadTime, + "requestType": requestType.rawValue, + "httpStatus": httpStatus, + "isSuccess": successful, + "size": size, + "url": url.absoluteString + ] + if let lastRedirectLocation { + json["lastRedirectLocation"] = lastRedirectLocation.absoluteString + } + return json + } +} diff --git a/ios/RNPlayerView+PlayerListener.swift b/ios/RNPlayerView+PlayerListener.swift index 9f5a48f4..01031d1e 100644 --- a/ios/RNPlayerView+PlayerListener.swift +++ b/ios/RNPlayerView+PlayerListener.swift @@ -121,6 +121,10 @@ extension RNPlayerView: PlayerListener { onSubtitleChanged?(event.toJSON()) } + public func onDownloadFinished(_ event: DownloadFinishedEvent, player: Player) { + onDownloadFinished?(event.toJSON()) + } + public func onAdBreakFinished(_ event: AdBreakFinishedEvent, player: Player) { onAdBreakFinished?(event.toJSON()) } diff --git a/ios/RNPlayerView.swift b/ios/RNPlayerView.swift index b4f0e1b2..0c2bf8a0 100644 --- a/ios/RNPlayerView.swift +++ b/ios/RNPlayerView.swift @@ -33,6 +33,7 @@ public class RNPlayerView: UIView { @objc var onSubtitleAdded: RCTBubblingEventBlock? @objc var onSubtitleRemoved: RCTBubblingEventBlock? @objc var onSubtitleChanged: RCTBubblingEventBlock? + @objc var onDownloadFinished: RCTBubblingEventBlock? @objc var onPictureInPictureEnter: RCTBubblingEventBlock? @objc var onPictureInPictureEntered: RCTBubblingEventBlock? @objc var onPictureInPictureExit: RCTBubblingEventBlock? diff --git a/ios/RNPlayerViewManager.m b/ios/RNPlayerViewManager.m index b9c94d5e..999709c0 100644 --- a/ios/RNPlayerViewManager.m +++ b/ios/RNPlayerViewManager.m @@ -32,6 +32,7 @@ @interface RCT_EXTERN_REMAP_MODULE(NativePlayerView, RNPlayerViewManager, RCTVie RCT_EXPORT_VIEW_PROPERTY(onSubtitleAdded, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onSubtitleRemoved, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onSubtitleChanged, RCTBubblingEventBlock) +RCT_EXPORT_VIEW_PROPERTY(onDownloadFinished, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureEnter, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureEntered, RCTBubblingEventBlock) RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureExit, RCTBubblingEventBlock) diff --git a/src/components/PlayerView/events.ts b/src/components/PlayerView/events.ts index 5fea2e44..c72da8d3 100644 --- a/src/components/PlayerView/events.ts +++ b/src/components/PlayerView/events.ts @@ -60,6 +60,7 @@ import { TimeChangedEvent, UnmutedEvent, VideoPlaybackQualityChangedEvent, + DownloadFinishedEvent, } from '../../events'; /** @@ -170,6 +171,10 @@ interface EventProps { * Event emitted when the player is destroyed. */ onDestroy: DestroyEvent; + /** + * Emitted when a download was finished. + */ + onDownloadFinished: DownloadFinishedEvent; /** * All events emitted by the player. */ diff --git a/src/components/PlayerView/index.tsx b/src/components/PlayerView/index.tsx index 718a4479..5e67d38e 100644 --- a/src/components/PlayerView/index.tsx +++ b/src/components/PlayerView/index.tsx @@ -213,6 +213,7 @@ export function PlayerView({ onTimeChanged={proxy(props.onTimeChanged)} onUnmuted={proxy(props.onUnmuted)} onVideoPlaybackQualityChanged={proxy(props.onVideoPlaybackQualityChanged)} + onDownloadFinished={proxy(props.onDownloadFinished)} /> ); } diff --git a/src/events.ts b/src/events.ts index 7cb2540a..6574fbcb 100644 --- a/src/events.ts +++ b/src/events.ts @@ -646,3 +646,58 @@ export interface CastWaitingForDeviceEvent extends Event { */ castPayload: CastPayload; } + +/** + * Available HTTP request types. + */ +export enum HttpRequestType { + ManifestDash = 'manifest/dash', + ManifestHlsMaster = 'manifest/hls/master', + ManifestHlsVariant = 'manifest/hls/variant', + ManifestSmooth = 'manifest/smooth', + MediaProgressive = 'media/progressive', + MediaAudio = 'media/audio', + MediaVideo = 'media/video', + MediaSubtitles = 'media/subtitles', + MediaThumbnails = 'media/thumbnails', + DrmLicenseFairplay = 'drm/license/fairplay', + DrmCertificateFairplay = 'drm/certificate/fairplay', + DrmLicenseWidevine = 'drm/license/widevine', + KeyHlsAes = 'key/hls/aes', + Unknown = 'unknown', +} + +/** + * Emitted when a download was finished. + */ +export interface DownloadFinishedEvent extends Event { + /** + * The time needed to finish the request, in seconds. + */ + downloadTime: number; + /** + * Which type of request this was. + */ + requestType: HttpRequestType; + /** + * The HTTP status code of the request. + * If opening the connection failed, a value of `0` is returned. + */ + httpStatus: number; + /** + * If the download was successful. + */ + isSuccess: boolean; + /** + * The last redirect location, or `null` if no redirect happened. + */ + lastRedirectLocation?: String; + /** + * The size of the downloaded data, in bytes. + */ + size: number; + /** + * The URL of the request. + */ + url: String; +}