Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions Shared/Components/Marquee.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct Marquee<Content>: View where Content: View {
delay: Double = 2.0,
gap: CGFloat = 50.0,
animateWhenFocused: Bool = false,
fade: CGFloat = 10.0,
fade: CGFloat = 10.0
) where Content == Text {
self.speed = speed
self.delay = delay
Expand Down Expand Up @@ -225,7 +225,7 @@ private struct _OffsetEffect: GeometryEffect {
set { offset = CGSize(width: newValue.first, height: newValue.second) }
}

public func effectValue(size _: CGSize) -> ProjectionTransform {
func effectValue(size _: CGSize) -> ProjectionTransform {
ProjectionTransform(CGAffineTransform(translationX: offset.width, y: offset.height))
}
}
2 changes: 1 addition & 1 deletion Shared/Components/NativeVideoPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ struct NativeVideoPlayer: View {
}
.alert(
L10n.error,
isPresented: .constant(manager.error != nil),
isPresented: .constant(manager.error != nil)
) {
Button(L10n.close, role: .cancel) {
Container.shared.mediaPlayerManager.reset()
Expand Down
2 changes: 1 addition & 1 deletion Shared/Components/VideoPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ struct VideoPlayer: View {

.alert(
L10n.error,
isPresented: .constant(manager.error != nil),
isPresented: .constant(manager.error != nil)
) {
Button(L10n.close, role: .cancel) {
Container.shared.mediaPlayerManager.reset()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ extension NavigationRoute {
) {
IdentifyItemView.RemoteSearchResultView(
viewModel: viewModel,
result: result,
result: result
)
}
}
Expand Down
15 changes: 14 additions & 1 deletion Shared/Extensions/JellyfinAPI/BaseItemDto/BaseItemDto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -509,11 +509,24 @@ extension BaseItemDto {

let request = Paths.getItem(itemID: id, userID: userSession.user.id)
let response = try await userSession.client.send(request)

// A check against `id` would typically be done, but a plugin
// may have provided `self` or the response item and may not
// be invariant over `id`.

return response.value
}
}

extension BaseItemDto {

/// Determines if this item supports shuffle playback
var canShuffle: Bool {
switch type {
case .series, .boxSet, .collectionFolder, .folder, .playlist:
return true
default:
return false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension BaseItemPerson: Poster {
let imageRequestParameters = Paths.GetItemImageParameters(
maxWidth: scaleWidth ?? Int(maxWidth),
quality: quality,
tag: primaryImageTag,
tag: primaryImageTag
)

let imageRequest = Paths.getItemImage(
Expand Down
2 changes: 1 addition & 1 deletion Shared/Extensions/ViewExtensions/ViewExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ extension View {
_ radius: CGFloat,
corners: RectangleCorner = .allCorners,
style: RoundedCornerStyle = .circular,
container: Bool = false,
container: Bool = false
) -> some View {
// Note: UnevenRoundedRectangle with all equal radii has
// been found to perform worse than RoundedRectangle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension MediaPlayerItem {
videoPlayerType: VideoPlayerType = Defaults[.VideoPlayer.videoPlayerType],
requestedBitrate: PlaybackBitrate = Defaults[.VideoPlayer.Playback.appMaximumBitrate],
compatibilityMode: PlaybackCompatibility = Defaults[.VideoPlayer.Playback.compatibilityMode],
modifyItem: ((inout BaseItemDto) -> Void)? = nil,
modifyItem: ((inout BaseItemDto) -> Void)? = nil
) async throws -> MediaPlayerItem {

let logger = Logger.swiftfin()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class AVMediaPlayerProxy: VideoMediaPlayerProxy {
private var statusObserver: NSKeyValueObservation!
private var timeControlStatusObserver: NSKeyValueObservation!
private var timeObserver: Any!
private var playbackEndObserver: NSObjectProtocol?
private var managerItemObserver: AnyCancellable?
private var managerStateObserver: AnyCancellable?

Expand Down Expand Up @@ -129,6 +130,12 @@ extension AVMediaPlayerProxy {

private func playbackStopped() {
player.pause()

if let playbackEndObserver {
NotificationCenter.default.removeObserver(playbackEndObserver)
self.playbackEndObserver = nil
}

guard let timeObserver else { return }
player.removeTimeObserver(timeObserver)
// rateObserver.invalidate()
Expand All @@ -144,6 +151,21 @@ extension AVMediaPlayerProxy {

player.replaceCurrentItem(with: newAVPlayerItem)

// Remove previous observer if any
if let playbackEndObserver {
NotificationCenter.default.removeObserver(playbackEndObserver)
}

// Observe when playback ends naturally
playbackEndObserver = NotificationCenter.default.addObserver(
forName: .AVPlayerItemDidPlayToEndTime,
object: newAVPlayerItem,
queue: .main
) { [weak self] _ in
guard let self else { return }
self.manager?.ended()
}

// TODO: protect against paused
// rateObserver = player.observe(\.rate, options: [.new, .initial]) { _, value in
// DispatchQueue.main.async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@ class NowPlayableObserver: ViewModel, MediaPlayerObserver {
.skipBackward,
.skipForward,
.changePlaybackPosition,
// TODO: only register next/previous if there is a queue
// .nextTrack,
// .previousTrack,
.nextTrack,
.previousTrack,
]
}

private var queueCommands: [NowPlayableCommand] {
[
.nextTrack,
.previousTrack,
]
}

Expand Down Expand Up @@ -69,6 +75,10 @@ class NowPlayableObserver: ViewModel, MediaPlayerObserver {
.sink { [weak self] newValue in self?.secondsDidChange(newValue) }
.store(in: &cancellables)

manager.$queue
.sink { [weak self] newValue in self?.queueDidChange(newValue) }
.store(in: &cancellables)

Notifications[.avAudioSessionInterruption]
.publisher
.sink { i in
Expand Down Expand Up @@ -106,6 +116,31 @@ class NowPlayableObserver: ViewModel, MediaPlayerObserver {
)
}

private func queueDidChange(_ newQueue: AnyMediaPlayerQueue?) {
if let queue = newQueue {
queue.$hasNextItem
.sink { [weak self] hasNext in
self?.updateQueueCommandAvailability(hasNext: hasNext, hasPrevious: queue.hasPreviousItem)
}
.store(in: &cancellables)

queue.$hasPreviousItem
.sink { [weak self] hasPrevious in
self?.updateQueueCommandAvailability(hasNext: queue.hasNextItem, hasPrevious: hasPrevious)
}
.store(in: &cancellables)

updateQueueCommandAvailability(hasNext: queue.hasNextItem, hasPrevious: queue.hasPreviousItem)
} else {
updateQueueCommandAvailability(hasNext: false, hasPrevious: false)
}
}

private func updateQueueCommandAvailability(hasNext: Bool, hasPrevious: Bool) {
NowPlayableCommand.nextTrack.isEnabled(hasNext)
NowPlayableCommand.previousTrack.isEnabled(hasPrevious)
}

private func actionDidChange(_ newAction: MediaPlayerManager._Action) {
switch newAction {
case .stop, .error:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ extension EpisodeMediaPlayerQueue {
}
}

private struct EpisodePreview: View {
struct EpisodePreview: View {

@Default(.accentColor)
private var accentColor
Expand Down Expand Up @@ -449,7 +449,7 @@ extension EpisodeMediaPlayerQueue {
}
}

private struct EpisodeDescription: View {
struct EpisodeDescription: View {

let episode: BaseItemDto

Expand All @@ -468,7 +468,7 @@ extension EpisodeMediaPlayerQueue {
}
}

private struct EpisodeRow: View {
struct EpisodeRow: View {

@Default(.accentColor)
private var accentColor
Expand Down Expand Up @@ -506,7 +506,7 @@ extension EpisodeMediaPlayerQueue {
}
}

private struct EpisodeButton: View {
struct EpisodeButton: View {

@Default(.accentColor)
private var accentColor
Expand Down
Loading