Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
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
24 changes: 22 additions & 2 deletions Model/Player/Backends/AVPlayerBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ final class AVPlayerBackend: PlayerBackend {

private var frequentTimeObserver: Any?
private var infrequentTimeObserver: Any?
private var playerTimeControlStatusObserver: Any?
private var playerTimeControlStatusObserver: NSKeyValueObservation?

private var statusObservation: NSKeyValueObservation?

Expand All @@ -119,6 +119,26 @@ final class AVPlayerBackend: PlayerBackend {
#if os(iOS)
controller.player = avPlayer
#endif
logger.info("AVPlayerBackend initialized.")
}

deinit {
// Invalidate any observers to avoid memory leaks
statusObservation?.invalidate()
playerTimeControlStatusObserver?.invalidate()

// Remove any time observers added to AVPlayer
if let frequentObserver = frequentTimeObserver {
avPlayer.removeTimeObserver(frequentObserver)
}
if let infrequentObserver = infrequentTimeObserver {
avPlayer.removeTimeObserver(infrequentObserver)
}

// Remove notification observers
removeItemDidPlayToEndTimeObserver()

logger.info("AVPlayerBackend deinitialized.")
}

func canPlay(_ stream: Stream) -> Bool {
Expand Down Expand Up @@ -779,7 +799,7 @@ final class AVPlayerBackend: PlayerBackend {
opened = true
controller.startPictureInPicture()
} else {
print("PiP not possible, waited \(delay) seconds")
self.logger.info("PiP not possible, waited \(delay) seconds")
}
}
}
Expand Down
57 changes: 32 additions & 25 deletions Model/Player/PlayerModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -880,26 +880,29 @@ final class PlayerModel: ObservableObject {
}

func updateRemoteCommandCenter() {
let skipForwardCommand = MPRemoteCommandCenter.shared().skipForwardCommand
let skipBackwardCommand = MPRemoteCommandCenter.shared().skipBackwardCommand
let previousTrackCommand = MPRemoteCommandCenter.shared().previousTrackCommand
let nextTrackCommand = MPRemoteCommandCenter.shared().nextTrackCommand
let commandCenter = MPRemoteCommandCenter.shared()
let skipForwardCommand = commandCenter.skipForwardCommand
let skipBackwardCommand = commandCenter.skipBackwardCommand
let previousTrackCommand = commandCenter.previousTrackCommand
let nextTrackCommand = commandCenter.nextTrackCommand

if !remoteCommandCenterConfigured {
remoteCommandCenterConfigured = true

#if !os(macOS)
try? AVAudioSession.sharedInstance().setCategory(
.playback,
mode: .moviePlayback
)

UIApplication.shared.beginReceivingRemoteControlEvents()
#endif

let interval = TimeInterval(systemControlsSeekDuration) ?? 10
let preferredIntervals = [NSNumber(value: interval)]

// Remove existing targets to avoid duplicates
skipForwardCommand.removeTarget(nil)
skipBackwardCommand.removeTarget(nil)
previousTrackCommand.removeTarget(nil)
nextTrackCommand.removeTarget(nil)
commandCenter.playCommand.removeTarget(nil)
commandCenter.pauseCommand.removeTarget(nil)
commandCenter.togglePlayPauseCommand.removeTarget(nil)
commandCenter.changePlaybackPositionCommand.removeTarget(nil)

// Re-add targets for handling commands
skipForwardCommand.preferredIntervals = preferredIntervals
skipBackwardCommand.preferredIntervals = preferredIntervals

Expand All @@ -923,22 +926,22 @@ final class PlayerModel: ObservableObject {
return .success
}

MPRemoteCommandCenter.shared().playCommand.addTarget { [weak self] _ in
commandCenter.playCommand.addTarget { [weak self] _ in
self?.play()
return .success
}

MPRemoteCommandCenter.shared().pauseCommand.addTarget { [weak self] _ in
commandCenter.pauseCommand.addTarget { [weak self] _ in
self?.pause()
return .success
}

MPRemoteCommandCenter.shared().togglePlayPauseCommand.addTarget { [weak self] _ in
commandCenter.togglePlayPauseCommand.addTarget { [weak self] _ in
self?.togglePlay()
return .success
}

MPRemoteCommandCenter.shared().changePlaybackPositionCommand.addTarget { [weak self] remoteEvent in
commandCenter.changePlaybackPositionCommand.addTarget { [weak self] remoteEvent in
guard let event = remoteEvent as? MPChangePlaybackPositionCommandEvent else { return .commandFailed }

self?.backend.seek(to: event.positionTime, seekType: .userInteracted)
Expand Down Expand Up @@ -1017,26 +1020,30 @@ final class PlayerModel: ObservableObject {
guard activeBackend == .mpv else { return }
#endif

#if os(iOS)
if activeBackend == .appleAVPlayer, avPlayerUsesSystemControls {
return
}
#endif

guard let video = currentItem?.video else {
MPNowPlayingInfoCenter.default().nowPlayingInfo = .none
return
}

let currentTime = (backend.currentTime?.seconds.isFinite ?? false) ? backend.currentTime!.seconds : 0

// Determine the media type based on musicMode
let mediaType: NSNumber
if musicMode {
mediaType = MPMediaType.anyAudio.rawValue as NSNumber
} else {
mediaType = MPMediaType.anyVideo.rawValue as NSNumber
}

// Prepare the Now Playing info dictionary
var nowPlayingInfo: [String: AnyObject] = [
MPMediaItemPropertyTitle: video.displayTitle as AnyObject,
MPMediaItemPropertyArtist: video.displayAuthor as AnyObject,
MPNowPlayingInfoPropertyIsLiveStream: live as AnyObject,
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime as AnyObject,
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
MPNowPlayingInfoPropertyPlaybackQueueIndex: 1 as AnyObject,
MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject
MPMediaItemPropertyMediaType: mediaType
]

if !currentArtwork.isNil {
Expand Down Expand Up @@ -1240,7 +1247,7 @@ final class PlayerModel: ObservableObject {
}

private func destroyKeyPressMonitor() {
if let keyPressMonitor = keyPressMonitor {
if let keyPressMonitor {
NSEvent.removeMonitor(keyPressMonitor)
}
}
Expand Down
18 changes: 16 additions & 2 deletions iOS/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import AVFoundation
import Foundation
import Logging
import UIKit

final class AppDelegate: UIResponder, UIApplicationDelegate {
var orientationLock = UIInterfaceOrientationMask.all

private var logger = Logger(label: "stream.yattee.app.delegalate")
private(set) static var instance: AppDelegate!

func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask {
Expand All @@ -12,11 +15,22 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {

func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { // swiftlint:disable:this discouraged_optional_collection
Self.instance = self
#if os(iOS)
UIViewController.swizzleHomeIndicatorProperty()

#if !os(macOS)
UIViewController.swizzleHomeIndicatorProperty()
OrientationTracker.shared.startDeviceOrientationTracking()

// Configure the audio session for playback
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
} catch {
logger.error("Failed to set audio session category: \(error)")
}

// Begin receiving remote control events
UIApplication.shared.beginReceivingRemoteControlEvents()
#endif

return true
}

Expand Down