From c32a3d7a1dfe569afe15742c74103fc7817d23df Mon Sep 17 00:00:00 2001 From: Stefano Russello Date: Thu, 14 Apr 2022 11:28:46 +0200 Subject: [PATCH] Update to version 1.1.0 Bitrate selector Fixed fullscreen button issue when rotating Modified default analytics url to https Optional IMA Plugin on createPlayer Dependency libraries updated Fixed issue #2 --- PlayKitReadme.md | 13 +- README.md | 2 +- .../bitrate_dark_gray.colorset/Contents.json | 38 ++++ .../Contents.json | 38 ++++ .../checkmark.imageset/Contents.json | 15 ++ .../checkmark.imageset/checkmark.svg | 3 + .../settingsButton.imageset/Contents.json | 15 ++ .../icons8-settings.svg | 1 + .../Player/Analytics/AMGAnalyticsPlugin.swift | 3 +- .../Analytics/AMGAnalyticsPluginConfig.swift | 2 +- .../Controls/AMGPlayKitStandardControl.swift | 205 ++++++++++++++---- ...itStandardControlsConfigurationModel.swift | 13 +- .../Player/Media/MediaContext.swift | 24 ++ .../Player/PlayKit/AMGPlayKit+Bitrate.swift | 61 ++++++ .../PlayKit/AMGPlayKit+Orientation.swift | 9 +- .../Player/PlayKit/AMGPlayKit+PiP.swift | 6 +- .../PlayKit/AMGPlayKit+StandardCasting.swift | 7 - .../PlayKit/AMGPlayKit+UIControls.swift | 2 - .../Player/PlayKit/AMGPlayKit.swift | 77 ++++++- .../Player/PlayKit/PlayKitProtocols.swift | 1 + StreamAMGSDK.podspec | 12 +- 21 files changed, 469 insertions(+), 78 deletions(-) create mode 100644 Source/Media/PlayKitMedia.xcassets/bitrate_dark_gray.colorset/Contents.json create mode 100644 Source/Media/PlayKitMedia.xcassets/bitrate_medium_gray.colorset/Contents.json create mode 100644 Source/Media/PlayKitMedia.xcassets/checkmark.imageset/Contents.json create mode 100644 Source/Media/PlayKitMedia.xcassets/checkmark.imageset/checkmark.svg create mode 100644 Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/Contents.json create mode 100644 Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/icons8-settings.svg create mode 100644 Source/StreamSDKPlayKit/Player/Media/MediaContext.swift create mode 100644 Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Bitrate.swift diff --git a/PlayKitReadme.md b/PlayKitReadme.md index 6dc4340..a65d87e 100644 --- a/PlayKitReadme.md +++ b/PlayKitReadme.md @@ -50,9 +50,10 @@ The following setup code should be carried out in the 'viewDidAppear' function: Create the player: ``` Swift -amgPlaykit?.createPlayer(analytics: AMGAnalyticsConfig? = nil) +amgPlaykit?.createPlayer(analytics: AMGAnalyticsConfig? = nil, enableIMA: Bool = true) ``` +'enableIMA' is an optional parameter, enabled by default, that it loads IMA Plugin (Optional) Add the partnerID - see 'Manually updating the PartnerID' ### Removing the player @@ -248,6 +249,11 @@ Set the colour of the VOD scrub bar 'tracked' time to a hex formatted RGB colour .scrubBarVODColour(colour: String) ``` +Toggle the visibility of the bitrate selector +``` Swift +.setBitrateSelector(_ isOn: Bool) +``` + ## Media overlays AMG Play Kit supports the overlaying of an 'is live' badge and a logo as overlays to any media playing. @@ -639,12 +645,15 @@ To instruct PlayKit to use a certain highest bitrate when streaming, you can use amgPlayKit?.setMaximumBitrate(bitrate: Double) ``` -PlayKit will atttempt to change bitrate to that value (or the closest one BELOW that value) for the rest of the stream. This change may not be immediate. +PlayKit will atttempt to change bitrate to that value (or the closest one BELOW that value) for the rest of the stream. # Change Log All notable changes to this project will be documented in this section. +### 1.1.0 +- Bitrate selector UI + ### 1.0.4 - Fixed fullscreen button to standard UI diff --git a/README.md b/README.md index 944f96a..ab5230a 100755 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Change Log: All notable changes to this project will be documented in this section. -### 1.0.4 - PlayKit minor UI fixes +### 1.1.0 - PlayKit bitrate selector UI ### 1.0.3 - PlayKit fixes diff --git a/Source/Media/PlayKitMedia.xcassets/bitrate_dark_gray.colorset/Contents.json b/Source/Media/PlayKitMedia.xcassets/bitrate_dark_gray.colorset/Contents.json new file mode 100644 index 0000000..29cefd2 --- /dev/null +++ b/Source/Media/PlayKitMedia.xcassets/bitrate_dark_gray.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x21", + "green" : "0x13", + "red" : "0x0D" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x21", + "green" : "0x13", + "red" : "0x0D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source/Media/PlayKitMedia.xcassets/bitrate_medium_gray.colorset/Contents.json b/Source/Media/PlayKitMedia.xcassets/bitrate_medium_gray.colorset/Contents.json new file mode 100644 index 0000000..d60c740 --- /dev/null +++ b/Source/Media/PlayKitMedia.xcassets/bitrate_medium_gray.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2D", + "green" : "0x24", + "red" : "0x21" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2D", + "green" : "0x24", + "red" : "0x21" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source/Media/PlayKitMedia.xcassets/checkmark.imageset/Contents.json b/Source/Media/PlayKitMedia.xcassets/checkmark.imageset/Contents.json new file mode 100644 index 0000000..3a8eda3 --- /dev/null +++ b/Source/Media/PlayKitMedia.xcassets/checkmark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "checkmark.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Source/Media/PlayKitMedia.xcassets/checkmark.imageset/checkmark.svg b/Source/Media/PlayKitMedia.xcassets/checkmark.imageset/checkmark.svg new file mode 100644 index 0000000..414de8d --- /dev/null +++ b/Source/Media/PlayKitMedia.xcassets/checkmark.imageset/checkmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/Contents.json b/Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/Contents.json new file mode 100644 index 0000000..bd0fa52 --- /dev/null +++ b/Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "icons8-settings.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/icons8-settings.svg b/Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/icons8-settings.svg new file mode 100644 index 0000000..4e7f09c --- /dev/null +++ b/Source/Media/PlayKitMedia.xcassets/settingsButton.imageset/icons8-settings.svg @@ -0,0 +1 @@ + diff --git a/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPlugin.swift b/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPlugin.swift index 62f54bc..3547ac3 100644 --- a/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPlugin.swift +++ b/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPlugin.swift @@ -19,8 +19,7 @@ public class AMGAnalyticsPlugin: BasePlugin, AnalyticsPluginProtocol { private static var partnerID = 0 - //private static var baseURL = "https://nudeiys3md.execute-api.eu-west-1.amazonaws.com/api/submit" - private static var baseURL = "http://stats.mp.streamamg.com/SessionUpdate?" + private static var baseURL = "https://stats.mp.streamamg.com/SessionUpdate?" // MARK: - Private local variables diff --git a/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPluginConfig.swift b/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPluginConfig.swift index 167df88..2f44322 100644 --- a/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPluginConfig.swift +++ b/Source/StreamSDKPlayKit/Player/Analytics/AMGAnalyticsPluginConfig.swift @@ -28,7 +28,7 @@ import PlayKit // MARK: - Properties /************************************************************/ - private let defaultBaseUrl = "http://stats.mp.streamamg.com/SessionUpdate" + private let defaultBaseUrl = "https://stats.mp.streamamg.com/SessionUpdate" /// application ID. let applicationId = Bundle.main.bundleIdentifier diff --git a/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControl.swift b/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControl.swift index 9b74ab8..fd6bcd7 100644 --- a/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControl.swift +++ b/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControl.swift @@ -22,13 +22,16 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { let backwardButton = UIButton(type: UIButton.ButtonType.custom) let fullscreenButton = UIButton(type: UIButton.ButtonType.custom) let minimiseButton = UIButton(type: UIButton.ButtonType.custom) + let settingsButton = UIButton(type: UIButton.ButtonType.custom) var playImage: UIImage = UIImage() var skipForawrdImage: UIImage = UIImage() var skipBackwardImage: UIImage = UIImage() var pauseImage: UIImage = UIImage() var fullScreenImage: UIImage = UIImage() + var settingsImage: UIImage = UIImage() var minimiseImage: UIImage = UIImage() var thumb: UIImage = UIImage() + var checkmark: UIImage = UIImage() var scrubBar: UISlider = UISlider() var scrubBarBackground: UIView = UIView() @@ -38,6 +41,9 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { var spoilerFreeRightView: UIView = UIView() + var bitrateView: UIView? = nil + + var liveButton = UIButton() var mediaLength: TimeInterval = 0 @@ -75,6 +81,12 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { private var bottomScrubViewTrack: UIView = UIView(frame: CGRect.zero) private var bottomTrackEnabled = false + + var bitrates: [Int64] = [] + var selectedBitrate = 0 + var bitrateColors: [UIColor] = [] + + var bitrateScroll: UIScrollView = UIScrollView(frame: .zero) // UIView = UIView() init (hostView: UIView, delegate: AMGPlayerDelegate, config: AMGPlayKitStandardControlsConfigurationModel? = nil){ super.init(frame: CGRect(x: 0, y: 0, width: hostView.frame.width, height: hostView.frame.height)) @@ -216,10 +228,26 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { } else if let myImage = UIImage(named: "minimiseButton", in: bundle, compatibleWith: .none){ minimiseImage = myImage } - +// if let customImage = configModel.minimiseImage, let myImage = UIImage(named: customImage) { +// minimiseImage = myImage +// } else + if let myImage = UIImage(named: "settingsButton", in: bundle, compatibleWith: .none){ + settingsImage = myImage + } if let myImage = UIImage(named: "slider_thumb", in: bundle, compatibleWith: .none){ thumb = myImage } + if let customImage = UIImage(named: "checkmark", in: bundle, compatibleWith: .none) { + checkmark = customImage + } + + if let color = UIColor(named: "bitrate_dark_gray", in: bundle, compatibleWith: .none) { + bitrateColors.append(color) + } + if let color = UIColor(named: "bitrate_medium_gray", in: bundle, compatibleWith: .none) { + bitrateColors.append(color) + } + playPause.frame = CGRect(x: x, y: y, width: playPauseSize, height: playPauseSize) playPause.tintColor = UIColor.white playPause.contentMode = .scaleToFill @@ -242,7 +270,7 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { mainView.addSubview(forwardButton) // Add scrub bar - let scrubBarBackY = h-65 + let scrubBarBackY = h-70 let scrubBarBackX = CGFloat(20) let scrubBarBackW = w-40 let scrubBarBackH = CGFloat(60) @@ -326,21 +354,32 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { if !hideFullScreenButton { - fullscreenButton.frame = CGRect(x: w - skipSize - 20, y: 20, width: skipSize, height: skipSize) - fullscreenButton.tintColor = UIColor.white - fullscreenButton.contentMode = .scaleToFill - fullscreenButton.setImage(fullScreenImage, for: .normal) - fullscreenButton.addTarget(self, action: #selector(fullScreenToggle), for: .touchUpInside) + fullscreenButton.frame = CGRect(x: w - skipSize - 20, y: 20, width: skipSize, height: skipSize) + fullscreenButton.tintColor = UIColor.white + fullscreenButton.contentMode = .scaleToFill + fullscreenButton.setImage(fullScreenImage, for: .normal) + fullscreenButton.addTarget(self, action: #selector(fullScreenToggle), for: .touchUpInside) mainView.addSubview(fullscreenButton) } - + if configModel.bitrateSelector { + settingsButton.frame = CGRect(x: w - skipSize - 20, y: h - skipSize - 5, width: skipSize, height: skipSize) + settingsButton.tintColor = UIColor.white + settingsButton.contentMode = .scaleToFill + settingsButton.setImage(settingsImage, for: .normal) + settingsButton.addTarget(self, action: #selector(openBitrateView), for: .touchUpInside) + settingsButton.layer.cornerRadius = 8 + mainView.addSubview(settingsButton) + } updateIsLive() updateSpoilerFree() showControls(false) + + //createBitrateView() } + func updateIsLive(){ var scrubBarx: CGFloat = 10 @@ -447,10 +486,29 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { player?.fullScreen() } } + + + @objc func closeBitrateView() { + bitrateView?.removeFromSuperview() + player?.startControlVisibilityTimer() + settingsButton.backgroundColor = .clear + } + + + @objc func openBitrateView() { + bitrateView = UIView.init(frame: self.bounds) + bitrateView?.backgroundColor = .clear // UIColor.init(red: 1, green: 1, blue: 1, alpha: 0.3) + let gesture = UITapGestureRecognizer(target: self, action: #selector(closeBitrateView)) + bitrateView?.addGestureRecognizer(gesture) + createBitrateSelector(withBitrateList: bitrates) + addSubview(bitrateView!) + player?.cancelTimer() + settingsButton.backgroundColor = bitrateColors.count > 0 ? bitrateColors.first : UIColor.black + } func play() { isPlaying = true - playPause.setImage(pauseImage, for: .normal) + playPause.setImage(pauseImage, for: .normal) } func pause() { @@ -460,29 +518,19 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { func changePlayHead(position: TimeInterval) { if !updatePlayHeadManually { - if position <= 0 { - scrubBar.value = 0 - } else if position >= mediaLength { - scrubBar.value = Float(mediaLength) - } else { - scrubBar.value = Float(position) - } - setTrackSize(position: position) - if trackTimeShowing { -// if currentTimeShowing { -// currentTime.text = timeForDisplay(time: position) -// startTime.text = "00:00" -// endTime.text = timeForDisplay(time: mediaLength) -// } else { - let timeRemaining = mediaLength - position + if position <= 0 { + scrubBar.value = 0 + } else if position >= mediaLength { + scrubBar.value = Float(mediaLength) + } else { + scrubBar.value = Float(position) + } + setTrackSize(position: position) + if trackTimeShowing { startTime.text = "\(timeForDisplay(time: position)) / \(timeForDisplay(time: mediaLength))" - - -// endTime.text = timeForDisplay(time: timeRemaining) -// } - } else if currentTimeShowing { - currentTime.text = timeForDisplay(time: position) - } + } else if currentTimeShowing { + currentTime.text = timeForDisplay(time: position) + } } if (position < mediaLength - 1) { liveButton.setTitle("GO LIVE", for: .normal) @@ -555,29 +603,32 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { forwardButton.frame = CGRect(x: x + playPauseSize + 20, y: skipY, width: skipSize, height: skipSize) -// let scrubBarBackY = h-65 -// let scrubBarBackX = CGFloat(20) -// let scrubBarBackW = w-40 -// let scrubBarBackH = CGFloat(40) - - let scrubBarBackY = h-65 + let scrubBarBackY = h-70 let scrubBarBackX = CGFloat(20) let scrubBarBackW = w-40 let scrubBarBackH = CGFloat(60) scrubBarBackground.frame = CGRect(x: scrubBarBackX, y: scrubBarBackY, width: scrubBarBackW, height: scrubBarBackH) - // scrubBarBackground.backgroundColor = UIColor.red spoilerFreeBackground.frame = CGRect(x: scrubBarBackX, y: scrubBarBackY, width: scrubBarBackW, height: scrubBarBackH) fullscreenButton.frame = CGRect(x: w - skipSize - 20, y: 20, width: skipSize, height: skipSize) + + settingsButton.frame = CGRect(x: w - skipSize - 20, y: h - skipSize - 5, width: skipSize, height: skipSize) + if (isFullScreen) { fullscreenButton.setImage(minimiseImage, for: .normal) } else { fullscreenButton.setImage(fullScreenImage, for: .normal) } + + if let bView = bitrateView { + bView.frame = self.bounds + self.bitrateScroll.heightAnchor.constraint(equalToConstant: min(bView.frame.height - 10, self.bitrateScroll.contentSize.height)).isActive = true + } + updateIsLive() updateSpoilerFree() } @@ -585,6 +636,65 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { func showControls(_ shouldShow: Bool) { mainView.isHidden = !shouldShow bottomScrubView.isHidden = shouldShow + closeBitrateView() + } + + + + func createBitrateSelector(withBitrateList: [Int64]){ + bitrates = withBitrateList + let maxWidth: CGFloat = 165 + var count = 1 + DispatchQueue.main.async { + self.bitrateScroll.removeFromSuperview() + self.bitrateScroll = UIScrollView(frame: .zero) + self.bitrateScroll.alwaysBounceVertical = false; + self.bitrateScroll.translatesAutoresizingMaskIntoConstraints = false + self.bitrateScroll.contentInsetAdjustmentBehavior = .never + self.bitrateScroll.contentSize = CGSize(width: maxWidth, height: CGFloat(self.bitrates.count + 1) * 48) + self.bitrateScroll.backgroundColor = .clear + self.bitrateScroll.layer.cornerRadius = 8 + + self.createBitrateLabel(text: "Auto", width: maxWidth, index: 0) + withBitrateList.forEach {bitrate in + self.createBitrateLabel(text: "\(bitrate)", width: maxWidth, index: count) + count += 1 + } + self.bitrateView?.addSubview(self.bitrateScroll) + + if let bView = self.bitrateView { + self.bitrateScroll.trailingAnchor.constraint(equalTo: bView.trailingAnchor, constant: -65).isActive = true + self.bitrateScroll.heightAnchor.constraint(equalToConstant: min(bView.frame.height - 10, self.bitrateScroll.contentSize.height)).isActive = true + self.bitrateScroll.bottomAnchor.constraint(equalTo: bView.bottomAnchor, constant: -5).isActive = true + self.bitrateScroll.widthAnchor.constraint(equalToConstant: maxWidth).isActive = true + } + } + } + + func createBitrateLabel(text: String, width: CGFloat, index: Int) { + let tText = UIButton(frame: CGRect(x: 0, y: CGFloat(48 * index), width: width, height: 48)) + tText.titleLabel?.font = UIFont.systemFont(ofSize: 16) + tText.setTitle(text, for: .normal) + tText.setTitleColor(.white, for: .normal) + tText.contentHorizontalAlignment = .left + tText.titleEdgeInsets = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0) + + if index == selectedBitrate { + tText.backgroundColor = bitrateColors.count > 0 ? bitrateColors.first : UIColor.black + tText.setImage(checkmark.withRenderingMode(.alwaysOriginal), for: .normal) + tText.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) + tText.imageEdgeInsets = UIEdgeInsets(top: 0, left: width - 30, bottom: 0, right: 0) + } else { + tText.backgroundColor = bitrateColors.count > 1 ? bitrateColors[1] : UIColor.darkGray + } + + tText.tag = index + tText.addTarget(self, action: #selector(swapBitRate(button:)), for: .touchUpInside) + + let divider = UIView(frame: CGRect(x: 0, y: 0, width: width, height: 0.5)) + divider.backgroundColor = bitrateColors.count > 0 ? bitrateColors.first : UIColor.black + tText.addSubview(divider) + self.bitrateScroll.addSubview(tText) } func removeStandardView(){ @@ -592,4 +702,23 @@ class AMGPlayKitStandardControl: UIView, AMGControlDelegate { playerView = nil } + @objc func swapBitRate(button: UIButton) { + let myTag = button.tag + if myTag == selectedBitrate || bitrates.count == 0 || tag > bitrates.count { + closeBitrateView() + return + } + + if myTag == 0 { + selectedBitrate = 0 + player?.setMaximumBitrate(bitrate: bitrates.last!) + closeBitrateView() + return + } + + selectedBitrate = myTag + player?.setMaximumBitrate(bitrate: bitrates[myTag - 1]) + closeBitrateView() + return + } } diff --git a/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControlsConfigurationModel.swift b/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControlsConfigurationModel.swift index 333a667..b653ae8 100644 --- a/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControlsConfigurationModel.swift +++ b/Source/StreamSDKPlayKit/Player/Controls/AMGPlayKitStandardControlsConfigurationModel.swift @@ -34,7 +34,7 @@ public struct AMGPlayKitStandardControlsConfigurationModel: Codable { var liveTrack = "#D0FF39" var vodTrack = "#71ABF3" - + var bitrateSelector = false } /** @@ -69,6 +69,7 @@ public class AMGControlBuilder { var liveTrack = "#D0FF39" var vodTrack = "#71ABF3" + var bitrateSelector = false /** Specify the image to use for the play button @@ -270,12 +271,20 @@ public class AMGControlBuilder { vodTrack = newColour return self } + + /** + Toggle the visibility of the bitrate selector + */ + public func setBitrateSelector(_ isOn: Bool) -> AMGControlBuilder { + bitrateSelector = isOn + return self + } /** Returns a complete and valid AMGPlayKitStandardControlsConfigurationModel */ public func build() -> AMGPlayKitStandardControlsConfigurationModel { - return AMGPlayKitStandardControlsConfigurationModel(fadeInTogglesPausePlay: fadeInTogglesPausePlay, fadeInTime: fadeInTime, fadeOutTime: fadeOutTime, fadeOutAfter: fadeOutAfter, trackTimeShowing: trackTimeShowing, isLiveImage: isLiveImage, logoImage: logoImage, playImage: playImage, pauseImage: pauseImage, skipForwardImage: skipForwardImage, skipBackwardImage: skipBackwardImage, fullScreenImage: fullScreenImage, hideFullscreen: hideFullscreen, hideMinimise: hideMinimise, liveTrack: liveTrack, vodTrack: vodTrack) + return AMGPlayKitStandardControlsConfigurationModel(fadeInTogglesPausePlay: fadeInTogglesPausePlay, fadeInTime: fadeInTime, fadeOutTime: fadeOutTime, fadeOutAfter: fadeOutAfter, trackTimeShowing: trackTimeShowing, isLiveImage: isLiveImage, logoImage: logoImage, playImage: playImage, pauseImage: pauseImage, skipForwardImage: skipForwardImage, skipBackwardImage: skipBackwardImage, fullScreenImage: fullScreenImage, hideFullscreen: hideFullscreen, hideMinimise: hideMinimise, liveTrack: liveTrack, vodTrack: vodTrack, bitrateSelector: bitrateSelector) diff --git a/Source/StreamSDKPlayKit/Player/Media/MediaContext.swift b/Source/StreamSDKPlayKit/Player/Media/MediaContext.swift new file mode 100644 index 0000000..f415b1b --- /dev/null +++ b/Source/StreamSDKPlayKit/Player/Media/MediaContext.swift @@ -0,0 +1,24 @@ +// +// MediaContext.swift +// StreamAMG +// +// Created by Mike Hall on 24/11/2021. +// + +import Foundation + +public struct MediaContext: Codable { + public let flavorAssets: [FlavorAsset] + + public func fetchBitrates() -> [Int64] { + return flavorAssets.compactMap {$0.bitrate}.sorted() + } +} + +public struct FlavorAsset: Codable { + public let width: Int64? + public let height: Int64? + public let bitrate: Int64? + public let id:String? + public let entryId:String? +} diff --git a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Bitrate.swift b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Bitrate.swift new file mode 100644 index 0000000..a122498 --- /dev/null +++ b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Bitrate.swift @@ -0,0 +1,61 @@ +// +// AMGPlayKit+Bitrate.swift +// StreamAMG +// +// Created by Mike Hall on 24/11/2021. +// + + +import Foundation + +extension AMGPlayKit { + + func fetchContextData(completion: @escaping ((MediaContext?) -> Void)) { + if let med = currentMedia, let server = med.serverURL{ + guard let validURL = URL(string: "\(server)/api_v3/?service=baseEntry&action=getContextData&entryId=\(med.entryID)&\(validKS(ks: med.ks, trailing: true))contextDataParams:objectType=KalturaEntryContextDataParams&contextDataParams:flavorTags=all&format=1") + else { + completion(nil) + return + } + let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + var urlRequest = URLRequest(url: validURL) + urlRequest.httpMethod = "POST" + let request = session.dataTask(with: urlRequest) {data, response, error in + do { + if let data = data { + let responseObject = try JSONDecoder().decode(MediaContext.self, from: data) + completion(responseObject) + } else { + completion(nil) + } + } catch { + completion(nil) + } + } + request.resume() + } + } + + public func setMaximumBitrate(bitrate: Int64){ + if let media = currentMedia { + loadMedia(media: media, mediaType: currentMediaType, startPosition: Int64(player?.currentTime ?? 0), bitrate: Double(bitrate)) + } + } + + func setMaximumBitrate(bitrate: Double){ + if let media = currentMedia { + loadMedia(media: media, mediaType: currentMediaType, startPosition: Int64(player?.currentTime ?? 0), bitrate: bitrate) + } + } + + func updateBitrateSelector(){ + fetchContextData {data in + if let data = data { + self.controlUI?.createBitrateSelector(withBitrateList: data.fetchBitrates()) + } else { + self.controlUI?.createBitrateSelector(withBitrateList: []) + } + } + } + +} diff --git a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Orientation.swift b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Orientation.swift index 3f1097c..8329a73 100644 --- a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Orientation.swift +++ b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+Orientation.swift @@ -26,6 +26,11 @@ extension AMGPlayKit { func resizeScreen(){ playerView?.frame = self.bounds controlUI?.frame = self.bounds + if #available(iOS 13.0, *) { + self.controlUI?.setFullScreen(UIDevice.current.orientation.isValidInterfaceOrientation ? UIDevice.current.orientation.isLandscape : (UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isLandscape ?? false)) + } else { + self.controlUI?.setFullScreen(UIDevice.current.orientation.isValidInterfaceOrientation ? UIDevice.current.orientation.isLandscape : UIApplication.shared.statusBarOrientation.isLandscape) + } controlUI?.resize() } @@ -34,7 +39,7 @@ extension AMGPlayKit { return } orientationTime = Date().timeIntervalSince1970 - let value = UIInterfaceOrientation.portrait.rawValue + let value = UIInterfaceOrientation.portrait.rawValue UIDevice.current.setValue(value, forKey: "orientation") UIViewController.attemptRotationToDeviceOrientation() controlUI?.setFullScreen(false) @@ -47,7 +52,7 @@ extension AMGPlayKit { return } orientationTime = Date().timeIntervalSince1970 - var value = UIInterfaceOrientation.landscapeRight.rawValue + var value = UIInterfaceOrientation.landscapeRight.rawValue if UIApplication.shared.statusBarOrientation == .landscapeLeft { value = UIInterfaceOrientation.landscapeLeft.rawValue } diff --git a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+PiP.swift b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+PiP.swift index ec84c49..e368006 100644 --- a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+PiP.swift +++ b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+PiP.swift @@ -29,7 +29,7 @@ extension AMGPlayKit: AVPictureInPictureControllerDelegate { } } - public func setPictureInPictureDelegate(_ delegate: AMGPictureInPictureDelegate) { + public func setPictureInPictureDelegate(_ delegate: AMGPictureInPictureDelegate?) { pipDelegate = delegate } @@ -65,9 +65,9 @@ extension AMGPlayKit: AVPictureInPictureControllerDelegate { } @objc internal func enterBackground(){ - DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { + DispatchQueue.main.async { if let pip = self.pictureInPictureController, pip.isPictureInPictureActive { - self.player?.play() + self.player?.play() } } } diff --git a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+StandardCasting.swift b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+StandardCasting.swift index 20aa822..1180c00 100644 --- a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+StandardCasting.swift +++ b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+StandardCasting.swift @@ -53,13 +53,6 @@ extension AMGPlayKit: URLSessionTaskDelegate { } } - private func validKS(ks: String?)-> String { - if let ks = ks { - return "ks/\(ks)/" - } - return "" - } - func sendCastingURL(url: String?) { guard let url = url, let urlToCast = URL(string: url) else { return diff --git a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+UIControls.swift b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+UIControls.swift index d560793..22206ab 100644 --- a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+UIControls.swift +++ b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit+UIControls.swift @@ -159,6 +159,4 @@ extension AMGPlayKit { } - - } diff --git a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit.swift b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit.swift index 4fc41da..18cbbe9 100644 --- a/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit.swift +++ b/Source/StreamSDKPlayKit/Player/PlayKit/AMGPlayKit.swift @@ -18,7 +18,7 @@ import AVKit The SDK, at it's most basic, is a UIView, instantiated either programatically, or via Storyboard, that acts as a single point of reference for all Kaltura PlayKit functionality */ @objc public class AMGPlayKit: UIView, AMGPlayerDelegate { - + var playerView: PlayerView? = nil public var player: Player? var partnerID: Int = 0 @@ -53,12 +53,13 @@ import AVKit private var playerState: PlayerState? = nil - internal var pipDelegate: AMGPictureInPictureDelegate? + weak internal var pipDelegate: AMGPictureInPictureDelegate? internal var pictureInPictureController: AVPictureInPictureController? internal var pipPossibleObservation: NSKeyValueObservation? var analyticsConfiguration: AMGAnalyticsConfig = AMGAnalyticsConfig() + private var ima: Bool = true private var currentAdvert = "" @@ -133,11 +134,12 @@ import AVKit - Parameter analytics: A valid AMGAnalyticsConfig object or 'nil' if no analytics is to be used */ - public func createPlayer(analytics: AMGAnalyticsConfig? = nil){ + public func createPlayer(analytics: AMGAnalyticsConfig? = nil, enableIMA: Bool = true){ tap = UITapGestureRecognizer(target: self, action: #selector(self.bringControlToForeground(_:))) if let analyticsConfig = analytics { analyticsConfiguration = analyticsConfig } + ima = enableIMA setNeedsLayout() layoutIfNeeded() constructPlayKit() @@ -170,7 +172,9 @@ import AVKit } func constructPlayKit() { - PlayKitManager.shared.registerPlugin(IMAPlugin.self) + if ima { + PlayKitManager.shared.registerPlugin(IMAPlugin.self) + } switch analyticsConfiguration.analyticsService { case .AMGANALYTICS: PlayKitManager.shared.registerPlugin(AMGAnalyticsPlugin.self) @@ -284,10 +288,7 @@ import AVKit control?.changePlayHead(position: playHead) } } - - public func setMaximumBitrate(bitrate: Double){ - player?.settings.network.preferredPeakBitRate = bitrate - } + func playEventOccurred() { control?.play() @@ -297,6 +298,16 @@ import AVKit control?.pause() } + internal func validKS(ks: String?, trailing: Bool = false)-> String { + if let ks = ks, !ks.isEmpty { + if trailing { + return "ks=\(ks)&" + } + return "ks/\(ks)/" + } + return "" + } + /** Adds a Fairplay license provider, if required @@ -347,7 +358,11 @@ import AVKit default: break } - config[IMAPlugin.pluginName] = getIMAPluginConfig() + + if ima { + config[IMAPlugin.pluginName] = getIMAPluginConfig() + } + return PluginConfig(config: config) } @@ -361,9 +376,11 @@ import AVKit break } - var imaconfig: [String: Any] = [:] - imaconfig[IMAPlugin.pluginName] = getIMAPluginConfig() - player?.updatePluginConfig(pluginName: IMAPlugin.pluginName, config: PluginConfig(config: imaconfig)) + if ima { + var imaconfig: [String: Any] = [:] + imaconfig[IMAPlugin.pluginName] = getIMAPluginConfig() + player?.updatePluginConfig(pluginName: IMAPlugin.pluginName, config: PluginConfig(config: imaconfig)) + } return } @@ -382,6 +399,7 @@ import AVKit if let player = player { updatePluginConfig() player.prepare(media.media()) + updateBitrateSelector() if mediaType == .Live{ self.currentMediaType = .Live self.controlUI?.setIsLive() @@ -404,6 +422,41 @@ import AVKit } } + internal func loadMedia(media: MediaItem, mediaType: AMGMediaType, startPosition: Int64, bitrate: Double){ + currentMedia = media + currentMediaType = mediaType + player?.pause() + if partnerID > 0{ + if let player = player { + player.settings.network.preferredPeakBitRate = bitrate * 1024 + updatePluginConfig() + let config = media.media() + if startPosition > 0 { + config.startTime = TimeInterval(startPosition) + } + player.prepare(config) + updateBitrateSelector() + if mediaType == .Live{ + self.currentMediaType = .Live + self.controlUI?.setIsLive() + } else { + isLive(){response in + DispatchQueue.main.async { + if response { + self.currentMediaType = .Live + self.controlUI?.setIsLive() + } else { + self.currentMediaType = .VOD + self.controlUI?.setIsVOD() + } + } + } + } + player.play() + } + } + } + /** Queues and runs the specified media item if available diff --git a/Source/StreamSDKPlayKit/Player/PlayKit/PlayKitProtocols.swift b/Source/StreamSDKPlayKit/Player/PlayKit/PlayKitProtocols.swift index 4034558..73e8683 100644 --- a/Source/StreamSDKPlayKit/Player/PlayKit/PlayKitProtocols.swift +++ b/Source/StreamSDKPlayKit/Player/PlayKit/PlayKitProtocols.swift @@ -20,6 +20,7 @@ public protocol AMGPlayerDelegate: AnyObject { func minimise() func fullScreen() + func setMaximumBitrate(bitrate: Int64) } diff --git a/StreamAMGSDK.podspec b/StreamAMGSDK.podspec index ff4ddcd..e3e0ee9 100644 --- a/StreamAMGSDK.podspec +++ b/StreamAMGSDK.podspec @@ -2,7 +2,7 @@ Pod::Spec.new do |spec| spec.name = "StreamAMGSDK" - spec.version = "1.0.4" + spec.version = "1.1.0" spec.summary = "Stream AMG SDK" spec.swift_versions = "5" @@ -56,17 +56,17 @@ end spec.subspec 'PlayKit' do |playkit| playkit.source_files = "Source/StreamSDKPlayKit/**/*.*" -playkit.dependency 'PlayKit', '3.20.0' -playkit.dependency 'PlayKit_IMA', '1.10.0' -playkit.dependency 'PlayKitProviders', '1.11.0' -playkit.dependency 'PlayKitYoubora', '1.9.0' +playkit.dependency 'PlayKit', '3.25.0' +playkit.dependency 'PlayKit_IMA', '1.11.0' +playkit.dependency 'PlayKitProviders', '1.16.0' +playkit.dependency 'PlayKitYoubora', '1.12.0' playkit.resource_bundles = { 'AMGPlayKitBundle' => 'Source/Media/*.*'} end spec.subspec 'PlayKit2Go' do |playkit2go| playkit2go.source_files = "Source/StreamSDKPlayKit2Go/**/*.*" playkit2go.dependency "StreamAMGSDK/PlayKit" -playkit2go.dependency 'DownloadToGo', '3.15.0' +playkit2go.dependency 'DownloadToGo', '3.17.0' end end