fix(ios): notification controls (#4838)

* fix: update observer thread

* fix(ios): fix showNotificationControls not working when set during initialization

* fix(ios): set custom metadata on AVPlayerItem for now playing info

* refactor: warn when wront artwork url

* docs: update file header

* chore: warn when failed to load artwork image
This commit is contained in:
Kamil Moskała
2026-02-06 11:55:59 +01:00
committed by GitHub
parent a1cc9eb7e3
commit 8da799832a
3 changed files with 62 additions and 3 deletions
@@ -0,0 +1,18 @@
//
// AVMetadataItem+make.swift
// ReactNativeVideo
//
// Created by Kamil Moskała on 06/02/2026.
//
import AVFoundation
extension AVMetadataItem {
static func make(identifier: AVMetadataIdentifier, value: NSObjectProtocol & NSCopying) -> AVMutableMetadataItem {
let item = AVMutableMetadataItem()
item.identifier = identifier
item.value = value
item.extendedLanguageTag = "und"
return item
}
}
@@ -114,7 +114,7 @@ class NowPlayingInfoCenterManager {
updateNowPlayingInfo()
playbackObserver = player.addPeriodicTimeObserver(
forInterval: CMTime(value: 1, timescale: 4),
queue: .global(),
queue: .main,
using: { [weak self] _ in
self?.updateNowPlayingInfo()
}
@@ -218,13 +218,16 @@ class NowPlayingInfoCenterManager {
}
public func updateNowPlayingInfo() {
guard let player = currentPlayer, let currentItem = player.currentItem
else {
guard let player = currentPlayer else {
invalidateCommandTargets()
MPNowPlayingInfoCenter.default().nowPlayingInfo = [:]
return
}
guard let currentItem = player.currentItem else {
return
}
// commonMetadata is metadata from asset, externalMetadata is custom metadata set by user
// externalMetadata should override commonMetadata to allow override metadata from source
// When the metadata has the tag "iTunSMPB" or "iTunNORM" then the metadata is not converted correctly and comes [nil, nil, ...]
@@ -391,6 +391,44 @@ class HybridVideoPlayer: HybridVideoPlayerSpec, NativeVideoPlayerSpec {
playerItem = AVPlayerItem(asset: asset)
}
if let metadata = source.config.metadata {
let title = metadata.title
let artist = metadata.artist
let imageUri = metadata.imageUri
DispatchQueue.main.async { [weak playerItem] in
guard let playerItem else { return }
var items: [AVMetadataItem] = []
if let title {
items.append(.make(identifier: .commonIdentifierTitle, value: title as NSString))
}
if let artist {
items.append(.make(identifier: .commonIdentifierArtist, value: artist as NSString))
}
if !items.isEmpty {
playerItem.externalMetadata = items
}
}
// Load artwork in background to not block player initialization
if let imageUri, let imageUrl = URL(string: imageUri) {
Task { [weak playerItem] in
guard let (data, _) = try? await URLSession.shared.data(from: imageUrl) else {
print("[RNV] Failed to load artwork from: \(imageUrl)")
return
}
DispatchQueue.main.async {
guard let playerItem else { return }
playerItem.externalMetadata = playerItem.externalMetadata + [.make(identifier: .commonIdentifierArtwork, value: data as NSData)]
NowPlayingInfoCenterManager.shared.updateNowPlayingInfo()
}
}
} else if let imageUri {
print("[RNV] Invalid imageUri for artwork: \(imageUri)")
}
}
return playerItem
}