I set a simple player with Bitmovin player iOS like this:
func bind(videoPlayer: BitmovinPlayer.Player, url: URL) {
// Update variables
self.videoPlayer = videoPlayer
self.videoPlayer?.add(listener: self)
self.videoPlayer?.config.networkConfiguration.preprocessHttpRequestDelegate = self
self.url = url
let analyticsConfig = BitmovinAnalyticsConfig(key: Constant.BitmovinAnalytics.LICENSE_KEY)
analyticsConfig.isLive = episode?.liveValue ?? false
analyticsCollector = BitmovinPlayerCollector(config: analyticsConfig)
analyticsCollector.attachPlayer(player: videoPlayer)
}
This method builds the player, but when I run a video the following method does not trigger the delegate:
func preprocessHttpRequest(_ type: String, httpRequest: HttpRequest, completionHandler: #escaping (HttpRequest) -> Void)
Other listeners work as expected.
Somebody had the same issue? I follow all the runcode looking for duplicate delegates but I just set the player once. No documentation or examples found for this feature.
On iOS, the preprocessHttpRequest is only supported for DRM requests (like DRM license acquisition) as per the Bitmovin Player iOS API documentation https://bitmovin.com/docs/player/api-reference/ios/ios-sdk-api-reference-v3#/player/ios/3/docs/Protocols/PreprocessHttpRequestDelegate.html:
Only changing DRM requests is currently supported.
Related
I am implementing offline playback of some HLS/m3u8 streams. Everything is working as intended using AVAssetDownloadURLSession, using it to make AVAssetDownloadTasks given an AVURLAsset from a stream url.
I would like to save some custom information in the asset's metadata property before or after the download is complete, but it is read only. I have tried using AVAssetExportSession, AVAssetWriter, etc. but none have worked due to (I think) the special way the OS manages the HLS offline playback files. They are packaged as an .movpkg
Anyone have experience with the above and gotten it to work?
The session is currently set up like this:
private lazy var avAssetDownloadSession = AVAssetDownloadURLSession(configuration: downloadConfig, assetDownloadDelegate: self, delegateQueue: .main)
private let downloadConfig: URLSessionConfiguration
init() {
self.downloadConfig = URLSessionConfiguration.background(withIdentifier: "DownloadConfig")
self.downloadConfig.httpMaximumConnectionsPerHost = 1
}
private func startDownload(for asset: AVURLAsset) {
guard let downloadTask = avAssetDownloadSession.makeAssetDownloadTask(asset: asset, assetTitle: "Test", assetArtworkData: nil, options: nil)
else { return }
downloadTask.taskDescription = "Test task description"
downloadTask.resume()
}
The delegate methods are all firing appropriately, so there's no problem w/ the download part.
This Adding meta-data to video in iOS link might be helpful.
Modifying downloaded movpkg's is not supported. Any metadata must also exist in the version on the server.
I moved step by step for getting rich push notifications. Here they are :
Created Notification service extension with plist :
NotificationService didRecieve :
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
func failEarly() {
contentHandler(request.content)
}
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
// Get the custom data from the notification payload
if let data = request.content.userInfo as? [String: AnyObject] {
// Grab the attachment
// let notificationData = data["data"] as? [String: String]
if let urlString = data["attachment-url"], let fileUrl = URL(string: urlString as! String) {
// Download the attachment
URLSession.shared.downloadTask(with: fileUrl) { (location, response, error) in
if let location = location {
// Move temporary file to remove .tmp extension
let tmpDirectory = NSTemporaryDirectory()
let tmpFile = "file://".appending(tmpDirectory).appending(fileUrl.lastPathComponent)
let tmpUrl = URL(string: tmpFile)!
try! FileManager.default.moveItem(at: location, to: tmpUrl)
// Add the attachment to the notification content
if let attachment = try? UNNotificationAttachment(identifier: "video", url: tmpUrl, options:nil) {
self.bestAttemptContent?.attachments = [attachment]
}else if let attachment = try? UNNotificationAttachment(identifier: "image", url: tmpUrl, options:nil) {
self.bestAttemptContent?.attachments = [attachment]
}else if let attachment = try? UNNotificationAttachment(identifier: "audio", url: tmpUrl, options:nil) {
self.bestAttemptContent?.attachments = [attachment]
}else if let attachment = try? UNNotificationAttachment(identifier: "image.gif", url: tmpUrl, options: nil) {
self.bestAttemptContent?.attachments = [attachment]
}
}
// Serve the notification content
self.contentHandler!(self.bestAttemptContent!)
}.resume()
}
}
}
Configured AppId and provision profile for extension.
Rich notification is coming correctly :
But here are the issues I am facing :
didRecieve is not getting called. For that I attached the serviceExtension process to the app target and ran the app.
Note : Extension is getting called as soon as notification arrives but didRecieve is not called :
On opening the push notification (which has video attachment), nothing happens. Ideally it should get played.
If I have to open the video and play it, do I have to explicitly do something or extension will take care of that ?
Payload :
aps = {
alert = "This is what your message will look like! Type in your message in the text area and get a preview right here";
badge = 1;
"mutable-content" = 1;
sound = default;
};
"attachment-url" = "https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_1mb.mp4";
deeplinkurl = "";
"message_id" = 1609;
}
I did try going through following posts but that didn't help :
iOS10 UNNotificationServiceExtension not called
NotificationServiceExtension not called
UNNotificationServiceExtension not working on iPhone 5 (iOS 10)
Good news! Your service extension is indeed being called - the image on your notification is evidence of that. What is probably happening here is that you are unable to debug the extension using the workflow you are used to with applications.
Debugging notification extensions is not like debugging an app. Extensions are plug-ins to an iOS process outside your application. Just setting a breakpoint is not a reliable way to debug them. Instead:
Debugging A Notification Service Extension
Launch the app from Xcode or the device
In Xcode, select Attach To Process or PID By Name... from the Debug menu
Enter the name of your notification extension
Trigger a notification (by sending a push, etc.).
When the notification is delivered the service extension should launch in to the debugger. Service extensions are only relevant to remote (push) notifications, so you will need a device to troubleshoot them.
Debugging A Notification Content Extension
There are at least two ways. The steps shown above for a service extension also work for a content extension. The second method is more familiar but less reliable.
Select the extension scheme in Xcode using the toolbar
In the Product menu, select Edit Scheme...
Set the Executable to the parent application.
Set a breakpoint inside the content extension.
Now build and run your extension. It will launch the parent application.
Trigger a notification that will cause the content extension to load.
It's worth noting that adding logging using the logging framework can be very useful for debugging and troubleshooting as well.
Why The Video May Not Be Playing
iOS limits the size of content that can be presented in notifications. This is described in the documentation for UNNotificationAttachment. For video it is generally 50Mb. Make sure your video is as small as you can make it in terms of bytes, and of course provide a video that is sized appropriately for the device it will be played on. Do not try to play a 1080p video in a notification that is 400 points wide!
In practice it is almost always better to use HLS instead of downloading video, and present it in a content extension.
Another thing in your code that may be problematic is the identifiers you are assigning to your attachments. Identifiers should be unique. Typically this would be a reverse-domain notation string like your bundle ID followed by a UUID string. You could also use the original URL of the content followed by a UUID string. If you provide an empty string iOS will create a unique identifier for you.
With the user notifications framework having non-unique identifiers (for notifications, attachments, etc.) tends to cause difficult to track down issues inside the framework. For example, this can cause an attached watchOS device to crash.
If you want to implement "auto play" for your video - it is not clear from your question wether that is what you are describing - you will need to implement your own player functionality in a content extension.
If you are going to do that, again, HLS is the preferred way to display video in a notification. It usually uses less RAM, offers a better user experience and tends to be more stable.
I need to create a Audio Player for streamed URL (m3u8 format). I have created music player using AVPlayer. But I need to show visualizer for streamed song. I have tried different solution but not found any working example of it.
I have created visualizer using AVAudioPlayer(averagePower) but it won't support streamed URL.
Any help to show visualizer for AVPlayer? Thanks in advance.
I have also tried using MYAudioTapProcessor which most of the people suggested, but for streamed URL, tracks always returns null.
Added the MYAudioTapProcessor.h and MYAudioTapProcessor.m in project
//Initialization of player
let playerItem = AVPlayerItem( url:NSURL( string:"https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8" ) as! URL )
let audioPlayer: AVPlayer = AVPlayer(playerItem:playerItem)
//Added periodic time observer
audioPlayer!.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: DispatchQueue.main) { (CMTime) -> Void in
if audioPlayer!.currentItem?.status == .readyToPlay
{
if let playerItem: AVPlayerItem = audioPlayer!.currentItem {
print(playerItem.asset.tracks.count)
if (playerItem.asset.tracks) != nil {
self.tapProcessor = MYAudioTapProcessor(avPlayerItem: playerItem)
playerItem.audioMix = self.tapProcessor.audioMix
self.tapProcessor.delegate = self
}
}
}
}
//Delegate callback method for MYAudioTapProcessor
func audioTabProcessor(_ audioTabProcessor: MYAudioTapProcessor!, hasNewLeftChannelValue leftChannelValue: Float, rightChannelValue: Float) {
print("volume: \(leftChannelValue) : \(rightChannelValue)")
volumeSlider.value = leftChannelValue
}
Also tried by adding the "Track" observer.
playerItem.addObserver(self, forKeyPath: "tracks", options: NSKeyValueObservingOptions.new, context: nil);
Now if play mp3 file, the callback method calls but for m3u8 callback method doesn't call. The main reason for failing m3u8 URL is it always show tracks array count zero whereas for mp3 files tracks array has one item.
You cannot get tracks for HLS via AVPLayer. You should use progressive download or local file for getting audio tracks while playing media.
Since iOS 10, Apple has provided the support for downloading HLS (m3u8) video for offline viewing.
My question is: Is it necessary that we can only download HLS when it is being played ? Or we can just download when user press download button and show progress.
Does anyone has implemented this in Objective C version? Actually my previous App is made in Objective C. Now I want to add support for downloading HLS rather than MP4 (previously I was downloading MP4 for offline view).
I am really desperate to this. Please share thoughts or any code if implemented.
I used the apple code guid to download HLS content with the following code:
var configuration: URLSessionConfiguration?
var downloadSession: AVAssetDownloadURLSession?
var downloadIdentifier = "\(Bundle.main.bundleIdentifier!).background"
func setupAssetDownload(videoUrl: String) {
// Create new background session configuration.
configuration = URLSessionConfiguration.background(withIdentifier: downloadIdentifier)
// Create a new AVAssetDownloadURLSession with background configuration, delegate, and queue
downloadSession = AVAssetDownloadURLSession(configuration: configuration!,
assetDownloadDelegate: self,
delegateQueue: OperationQueue.main)
if let url = URL(string: videoUrl){
let asset = AVURLAsset(url: url)
// Create new AVAssetDownloadTask for the desired asset
let downloadTask = downloadSession?.makeAssetDownloadTask(asset: asset,
assetTitle: "Some Title",
assetArtworkData: nil,
options: nil)
// Start task and begin download
downloadTask?.resume()
}
}//end method
func urlSession(_ session: URLSession, assetDownloadTask: AVAssetDownloadTask, didFinishDownloadingTo location: URL) {
// Do not move the asset from the download location
UserDefaults.standard.set(location.relativePath, forKey: "testVideoPath")
}
if you don't understand what's going on, read up about it here:
https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/HTTPLiveStreaming/HTTPLiveStreaming.html
now you can use the stored HSL content to play the video in AVPlayer with the following code:
//get the saved link from the user defaults
let savedLink = UserDefaults.standard.string(forKey: "testVideoPath")
let baseUrl = URL(fileURLWithPath: NSHomeDirectory()) //app's home directory
let assetUrl = baseUrl.appendingPathComponent(savedLink!) //append the saved link to home path
now use the path to play video in AVPlayer
let avAssest = AVAsset(url: assetUrl)
let playerItem = AVPlayerItem(asset: avAssest)
let player = AVPlayer(playerItem: playerItem) // video path coming from above function
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true, completion: {
player.play()
})
The only way you can do this is to set up an HTTP server to serve the files locally after you've downloaded them.
The Live playlist uses a sliding-window. You need to periodically reload it after target-duration time and download only the new segments as they appear in the list (they will be removed at a later time).
Here are some related answers: Can IOS devices stream m3u8 segmented video from the local file system using html5 video and phonegap/cordova?
You can easily download an HLS stream with AVAssetDownloadURLSession makeAssetDownloadTask. Have a look at the AssetPersistenceManager in Apples Sample code: https://developer.apple.com/library/content/samplecode/HLSCatalog/Introduction/Intro.html
It should be fairly straight forward to use the Objective C version of the api.
Yes, you can download video stream served over HLS and watch it later.
There is a very straight forward sample app (HLSCatalog) from apple on this. The code is fairly simple. you can find it here - https://developer.apple.com/services-account/download?path=/Developer_Tools/FairPlay_Streaming_Server_SDK_v3.1/FairPlay_Streaming_Server_SDK_v3.1.zip
You can find more about offline HLS streaming here.
i'm trying to play an AES encrypted stream in AVPlayer.. typically a link of the key is delivered to the player inside the M3U8 playlist.. in my scenario the key is divided in half.. the first half is delivered by the server and i should append the other half inside the app to decrypt when playing
i've already done this on Android, is there a way to do it also on iOS?
This is the playlist:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=200000,RESOLUTION=284x160
chunklist_w670540365_b200000.m3u8?t=57b5b16d3824d
#EXT-X-STREAM-INF:BANDWIDTH=850000,RESOLUTION=640x360
chunklist_w670540365_b850000.m3u8?t=57b5b16d3824d
And this is the chunk list:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:11
#EXT-X-MEDIA-SEQUENCE:13544
#EXT-X-KEY:METHOD=AES-128,URI="http://example.com/api/getEncryptionKey?t=57b5b16d3824d"
#EXTINF:9.6,
media_w670540365_b200000_13544.ts?t=57b5b16d3824d
#EXTINF:9.6,
media_w670540365_b200000_13545.ts?t=57b5b16d3824d
#EXTINF:10.56,
media_w670540365_b200000_13546.ts?t=57b5b16d3824d
This is what AVPlayer does:
1- the playlist gets downloaded and a chunk list is selected
2- the player downloads the chunk list
3- the decryption key to decrypt the chunks is downloaded
4- the player begins downloading the chunks sequentially to play them
5- every chunk is decrypted and played
What i need to do is:
after the 3rd step when the player calls the api to get the encryption key using this link: 'http://example.com/api/getEncryptionKey?t=57b5b16d3824d', i want to intercept the response and append the other half of the key
Is it possible?
yes, it is very much possible! I recently did it in one of my projects.
Whenever AVPlayer loads the encrypted video, it tries to load decryption key from the URL mentioned in prog_index.m3u8. If AVPlayer is not able to play video with the fetched key or if it didn't get the key at all on the specified url, it calls a delegate method from AVAssetResourceLoaderDelegate that is
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForRenewalOfRequestedResource renewalRequest: AVAssetResourceRenewalRequest) -> Bool {
return shouldLoadOrRenewRequestedResource(resourceLoadingRequest: renewalRequest)
}
and,
public func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
return shouldLoadOrRenewRequestedResource(resourceLoadingRequest: loadingRequest)
}
which of course differ in the cases they are being called. Prior one is called when the player should wait for loading resource and later one is called when the player needs to renew the resource.
func shouldLoadOrRenewRequestedResource(resourceLoadingRequest: AVAssetResourceLoadingRequest) -> Bool {
guard var url = resourceLoadingRequest.request.url else {
return false
}
//FETCH THE KEY FROM NETWORK CALL/KEYSTORE, CONVERT IT TO DATA AND FINISH LOADING OF RESOURCE WITH THAT DATA, IN YOUR CASE JOIN THE OTHER HALF OF THE KEY TO ACTUAL KEY (you can get the first half from the url above)
resourceLoadingRequest.dataRequest?.respond(with: keyData)
resourceLoadingRequest.finishLoading()
return true;
}}
Once you'll return true with the actual key, the video will be played instantly.
While trying with Azure Media services the following sample works fine. Here we are adding the token as part of AVURLAsset options.
var options = [String: [String: String]]()
if (!token.isEmpty) {
let headers = ["Authorization": "Bearer " + token!]
options = ["AVURLAssetHTTPHeaderFieldsKey": headers]
}
let avAsset = AVURLAsset(url: videoUrl, options: options)
let avItem = AVPlayerItem(asset: avAsset)
let player = AVPlayer(playerItem: avItem)
let playerFrame = view.viewWithTag(1)?.frame
controller.player = player
if (autoPlay.isOn) {
player.rate = 1
}
controller.view.frame = playerFrame ?? CGRect(x: 0, y: 0, width: view.frame.width , height: 250)
addChild(controller)
view.viewWithTag(1)?.addSubview(controller.view)
controller.didMove(toParent: self)
The complete working sample can be found from
https://github.com/Azure-Samples/media-services-3rdparty-player-samples/tree/master/src/avplayer