Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 59 additions & 22 deletions ios/Classes/RadioPlayerService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import AVKit

/// Manages the AVPlayer instance for streaming radio playback and handles audio session configuration.
class RadioPlayerService: NSObject {
private var player: AVPlayer!
var streamTitle: String!
var streamUrl: String!
private let player: AVPlayer
var streamTitle: String?
var streamUrl: String?
var defaultArtwork: UIImage?
private var metadataHash: String? = nil

Expand All @@ -26,9 +26,9 @@ class RadioPlayerService: NSObject {

/// Initialization
override init() {
player = AVPlayer()
super.init()

player = AVPlayer()
player.automaticallyWaitsToMinimizeStalling = true

// Adds observers for player properties.
Expand All @@ -48,13 +48,14 @@ class RadioPlayerService: NSObject {

commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.play()
return .success
guard let self = self else { return .commandFailed }
return self.playSafely() ? .success : .commandFailed
}

commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { [weak self] (event) -> MPRemoteCommandHandlerStatus in
self?.pause()
guard let self = self else { return .commandFailed }
self.pause()
return .success
}

Expand Down Expand Up @@ -85,23 +86,28 @@ class RadioPlayerService: NSObject {
func setStation(title: String, url: String, imageData: Data?, parseStreamMetadata: Bool, lookupOnlineArtwork: Bool) {
streamTitle = title
streamUrl = url
defaultArtwork = imageData != nil ? UIImage(data: imageData!) : nil
defaultArtwork = imageData.flatMap { UIImage(data: $0) }

// Update properties based on new parameters.
self.parseStreamMetadata = parseStreamMetadata
self.lookupOnlineArtwork = lookupOnlineArtwork

// Update Now Playing Info with station title initially.
var nowPlayingInfo: [String: Any] = [
MPMediaItemPropertyTitle: streamTitle!,
MPMediaItemPropertyTitle: title,
]
if let art = defaultArtwork {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: art.size, requestHandler: { _ in art })
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo

guard let streamUrl = streamUrl, let stationURL = URL(string: streamUrl) else {
player.replaceCurrentItem(with: nil)
return
}

// Create and set the new player item.
let playerItem = AVPlayerItem(url: URL(string: streamUrl)!)
let playerItem = AVPlayerItem(url: stationURL)
player.replaceCurrentItem(with: playerItem)

// Set metadata handler.
Expand Down Expand Up @@ -154,20 +160,51 @@ class RadioPlayerService: NSObject {
}
}

/// Starts or resumes playback.
func play() {
// If the player item is nil or has failed, try to set it up again.
if player.currentItem == nil || player.currentItem?.isPlaybackBufferEmpty == true || player.currentItem?.status == .failed {
setStation(
title: streamTitle,
url: streamUrl,
imageData: defaultArtwork?.jpegData(compressionQuality: 1.0),
parseStreamMetadata: parseStreamMetadata,
lookupOnlineArtwork: lookupOnlineArtwork
)
private func playSafely() -> Bool {
guard let title = streamTitle?.nilIfEmpty(),
let streamUrl = streamUrl?.nilIfEmpty(),
let stationURL = URL(string: streamUrl) else {
return false
}

let currentAssetURL = (player.currentItem?.asset as? AVURLAsset)?.url.absoluteString
let needsNewItem = player.currentItem == nil
|| player.currentItem?.status == .failed
|| player.currentItem?.isPlaybackBufferEmpty == true
|| currentAssetURL != stationURL.absoluteString

if needsNewItem {
let playerItem = AVPlayerItem(url: stationURL)

let metaOutput = AVPlayerItemMetadataOutput(identifiers: nil)
metaOutput.setDelegate(self, queue: DispatchQueue.main)
playerItem.add(metaOutput)

player.replaceCurrentItem(with: playerItem)
}

var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:]
nowPlayingInfo[MPMediaItemPropertyTitle] = title
if let art = defaultArtwork {
nowPlayingInfo[MPMediaItemPropertyArtwork] = MPMediaItemArtwork(boundsSize: art.size, requestHandler: { _ in art })
}
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo

do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("RadioPlayerSevice: Failed to activate audio session before play: \(error)")
return false
}

player.play()
return player.timeControlStatus == .playing || player.rate > 0
}

/// Starts or resumes playback.
func play() {
_ = playSafely()
}

/// Pauses playback.
Expand Down Expand Up @@ -244,7 +281,7 @@ class RadioPlayerService: NSObject {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
play()
_ = playSafely()
}
}

Expand Down