Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
57a790a
WIP
JPKribs Dec 31, 2025
cef9409
Merge branch 'main' into transcodeFix
JPKribs Jan 2, 2026
c1b1282
Media Source Fix
JPKribs Jan 2, 2026
f89ca6b
Linting
JPKribs Jan 2, 2026
ddc7363
Appropriately kill the old screen when changing the track
JPKribs Jan 2, 2026
af63b20
Merge branch 'main' into transcodeFix
JPKribs Jan 2, 2026
71d436a
Merge branch 'main' into transcodeFix
JPKribs Jan 8, 2026
8ebdd03
Fix https://github.com/jellyfin/Swiftfin/issues/1889
JPKribs Jan 9, 2026
553c2af
Indexes don't change.
JPKribs Jan 10, 2026
d7b7e4f
Cleanup
JPKribs Jan 10, 2026
67fb4e3
Reset
JPKribs Jan 11, 2026
5f19ac2
Reset
JPKribs Jan 11, 2026
a68ff96
Reset
JPKribs Jan 11, 2026
d35d67d
Merge branch 'main' into transcodeFix
JPKribs Jan 12, 2026
5ac2aa3
Audio & Video functional. Subtitles are broken on transcode.
JPKribs Jan 12, 2026
e26ad2c
Merge branch 'main' into transcodeFix
JPKribs Feb 19, 2026
6d72d52
WIP
JPKribs Feb 19, 2026
6514481
Merge branch 'main' into transcodeFix
JPKribs Feb 19, 2026
4dd6a23
`MediaStreamType` `supportedCases`
JPKribs Feb 19, 2026
20a22ef
External Image Subs need to encode or be embedded. No external.
JPKribs Feb 20, 2026
6822e07
Subtitle Fixes - Still need `Disable different types of embedded subt…
JPKribs Feb 20, 2026
b95df67
WIP
JPKribs Feb 20, 2026
416501a
Temporary logging cleanup & remove external PGS from UI when not able…
JPKribs Feb 20, 2026
5934b36
Update players.md to reflect feature changes
JPKribs Feb 20, 2026
77bd5ff
Revise subtitle format support details in players.md
JPKribs Feb 20, 2026
662b8d8
Merge branch 'main' into transcodeFix
JPKribs Feb 20, 2026
2662349
Remove unused extension
JPKribs Feb 20, 2026
5c90110
Clarify XSub subtitle format playback notes
JPKribs Feb 20, 2026
e6f949b
Update player controls and miscellaneous features
JPKribs Feb 20, 2026
2a7bff0
Correct capitalization in player controls section
JPKribs Feb 20, 2026
26aef13
Rename H.264 to H.264/AVC in players documentation
JPKribs Feb 20, 2026
6a36f78
Remove media source selection as this is in it's own PR now
JPKribs Feb 20, 2026
c933f2e
Merge remote-tracking branch 'refs/remotes/origin/transcodeFix'
JPKribs Feb 20, 2026
2670230
Preserve current ticks during the track change.
JPKribs Feb 20, 2026
4ad9ad2
Set using user data instead of manual overrides
JPKribs Feb 20, 2026
9efbfa7
Remove unneccessary rebuilding when PGS is embedded.
JPKribs Feb 22, 2026
8664960
Merge branch 'main' into transcodeFix
JPKribs Feb 22, 2026
48ddbab
Merge branch 'main' into transcodeFix
JPKribs Feb 23, 2026
9e88d19
Merge branch 'main' into transcodeFix
JPKribs Feb 25, 2026
d2e1c23
Merge branch 'main' into transcodeFix
JPKribs Feb 27, 2026
028608f
Merge branch 'main' into transcodeFix
JPKribs Mar 2, 2026
6b604e3
Merge branch 'main' into transcodeFix
JPKribs Mar 2, 2026
f080a66
Merge branch 'main' into transcodeFix
JPKribs Mar 6, 2026
38f07d3
Merge branch 'main' into transcodeFix
JPKribs Mar 7, 2026
8fef3e3
Merge branch 'main' into transcodeFix
JPKribs Mar 8, 2026
8bee619
Merge branch 'main' into transcodeFix
JPKribs Mar 9, 2026
63aafb5
Merge branch 'main' into transcodeFix
JPKribs Mar 11, 2026
6c8b972
Update comments to be 1) cleaner and 2) at the top of functions/var i…
JPKribs Mar 11, 2026
957d5ef
Merge branch 'main' into transcodeFix
JPKribs Mar 13, 2026
58cbac2
Merge branch 'main' into transcodeFix
JPKribs Mar 13, 2026
5fce8ae
Merge branch 'main' into transcodeFix
JPKribs Mar 13, 2026
a19c531
Merge branch 'main' into transcodeFix
JPKribs Mar 14, 2026
cb17c56
Merge branch 'jellyfin:main' into transcodeFix
JPKribs Mar 27, 2026
d0ea3c4
Implementation of AVProxy to utilize Playback Controls & all that
JPKribs Mar 29, 2026
b584996
Cleanup VLC vs Swiftfin and AVPlayer vs Native
JPKribs Mar 29, 2026
723a8f7
Build issues for setSeconds and defaults
JPKribs Mar 29, 2026
8131064
Use the right set
JPKribs Mar 29, 2026
018dc30
Locallizing
JPKribs Mar 29, 2026
4f5b999
WIP
JPKribs Mar 29, 2026
df7bae6
Merge branch 'jellyfin:main' into transcodeFix
JPKribs Mar 29, 2026
e1a40c3
Merge origin/transcodeFix into avplayer
JPKribs Mar 29, 2026
c7fac57
Fix Subtitles set to None
JPKribs Mar 31, 2026
04a1fba
Transmission Fixes
JPKribs Apr 7, 2026
5ef3f5d
Merge branch 'main' into avplayer
JPKribs Apr 20, 2026
cef9f97
Merge Cleanup
JPKribs Apr 20, 2026
6ce89cd
Merge remote-tracking branch 'upstream/main' into avplayer
JPKribs Apr 28, 2026
46a1ab6
Merge Fixes
JPKribs Apr 28, 2026
74b32d6
Merge Fix
JPKribs May 6, 2026
c05bd0f
Merge remote-tracking branch 'origin/main' into avplayer
JPKribs May 20, 2026
a26c149
Merge Fix
JPKribs May 20, 2026
2662011
Merge branch 'jellyfin:main' into avplayer
JPKribs May 21, 2026
4b13310
Merge branch 'jellyfin:main' into avplayer
JPKribs May 21, 2026
b086dbf
Merge branch 'main' into avplayer
JPKribs Jun 11, 2026
129559a
WIP
JPKribs Jun 11, 2026
0145a91
Start over
JPKribs Jun 11, 2026
6c7572d
Merge branch 'avplayer' of https://github.com/JPKribs/Swiftfin into a…
JPKribs Jun 11, 2026
ac1e998
tvOS PiP for some reason...
JPKribs Jun 11, 2026
662a3ac
Cleanup
JPKribs Jun 11, 2026
54ad7e9
WIP
JPKribs Jun 11, 2026
bd65a98
Airplay fixes
JPKribs Jun 12, 2026
189df01
WIP
JPKribs Jun 13, 2026
341c62a
WIP
JPKribs Jun 14, 2026
416898b
Scope Creep - This is a complete mess.........
JPKribs Jun 15, 2026
80eb441
Merge branch 'main' into avplayer
JPKribs Jun 15, 2026
b2a73f1
Merge Fixes
JPKribs Jun 15, 2026
02494d8
Build fixes
JPKribs Jun 15, 2026
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
115 changes: 0 additions & 115 deletions Shared/Components/NativeVideoPlayer.swift

This file was deleted.

58 changes: 58 additions & 0 deletions Shared/Components/RemotePlayback/PlaybackRoutePickerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2026 Jellyfin & Jellyfin Contributors
//

import AVKit
import SwiftUI

struct PlaybackRoutePickerView: UIViewRepresentable {

@Binding
var present: Bool

let onBeginPresenting: () -> Void

func makeCoordinator() -> Coordinator {
Coordinator(onBeginPresenting: onBeginPresenting)
}

func makeUIView(context: Context) -> AVRoutePickerView {
let routePickerView = AVRoutePickerView()
routePickerView.delegate = context.coordinator
routePickerView.prioritizesVideoDevices = true
routePickerView.tintColor = .clear
routePickerView.activeTintColor = .clear
return routePickerView
}

func updateUIView(_ uiView: AVRoutePickerView, context: Context) {
guard present else { return }

DispatchQueue.main.async {
present = false

for subview in uiView.subviews {
if let button = subview as? UIButton {
button.sendActions(for: .touchUpInside)
}
}
}
}

class Coordinator: NSObject, AVRoutePickerViewDelegate {

private let onBeginPresenting: () -> Void

init(onBeginPresenting: @escaping () -> Void) {
self.onBeginPresenting = onBeginPresenting
}

func routePickerViewWillBeginPresentingRoutes(_ routePickerView: AVRoutePickerView) {
onBeginPresenting()
}
}
}
190 changes: 190 additions & 0 deletions Shared/Components/RemotePlayback/RemotePlaybackPickerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2026 Jellyfin & Jellyfin Contributors
//

import Defaults
import Factory
import JellyfinAPI
import OrderedCollections
import SwiftUI

struct RemotePlaybackPickerView: View {

@Default(.accentColor)
private var accentColor
@Default(.VideoPlayer.mediaPlaybackStrategy)
private var mediaPlaybackStrategy

@Router
private var router

@InjectedObject(\.mediaPlayerManager)
private var manager: MediaPlayerManager

@State
private var presentRoutePicker: Bool = false

@StateObject
private var viewModel = ActiveSessionsViewModel()

private var castableSessions: [SessionInfoDto] {
viewModel.sessions.values
.compactMap(\.value)
.filter {
$0.isSupportsRemoteControl ?? false
}
}

private func isCasting(to session: SessionInfoDto) -> Bool {
guard let active = manager.remote.activeSession, active.route == .jellyfin else { return false }
return active.deviceName == (session.deviceName ?? session.client)
}

private func toggleCast(to session: SessionInfoDto) {
if isCasting(to: session) {
manager.remote.end(route: .jellyfin)
} else {
Task { await manager.remote.select(JellyfinPlaybackSession(session: session)) }
}

router.dismiss()
}

private var isAirPlayAvailable: Bool {
mediaPlaybackStrategy != .player(.vlc)
}

private var hasNoTargets: Bool {
!isAirPlayAvailable && castableSessions.isEmpty
}

var body: some View {
List {
FormItemSection(item: manager.item)

if isAirPlayAvailable {
Section {
airPlayRow
}
}

if castableSessions.isNotEmpty {
Section(L10n.devices) {
ForEach(castableSessions, id: \.id) { session in
sessionRow(session)
}
}
}

if hasNoTargets {
Section {
// swiftlint:disable:next hard_coded_display_string
Text("No valid targets")
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, alignment: .center)
}
}
}
.navigationTitle(L10n.output)
.navigationBarTitleDisplayMode(.inline)
.if(UIDevice.isPhone) { view in
view
.presentationDetents([.medium, .large])
}
.onFirstAppear {
viewModel.refresh()
}
.navigationBarCloseButton {
router.dismiss()
}
}

private var isAirPlaying: Bool {
manager.remote.state?.type == .airPlay
}

private var airPlayRow: some View {
ListRow {
Image(systemName: "airplayvideo")
.resizable()
.aspectRatio(contentMode: .fit)
.padding(8)
.frame(width: 60, height: 60)
.foregroundStyle(isAirPlaying ? AnyShapeStyle(accentColor) : AnyShapeStyle(.primary))
} content: {
HStack {
// swiftlint:disable:next hard_coded_display_string
Text("\(L10n.airPlay) & \(L10n.bluetooth)")
.font(.headline)

Spacer()

ListRowCheckbox()
}
} action: {
presentRoutePicker = true
}
.isSeparatorVisible(false)
.isSelected(isAirPlaying)
.background {
PlaybackRoutePickerView(present: $presentRoutePicker) {
router.dismiss()
}
.frame(width: 1, height: 1)
.allowsHitTesting(false)
}
}

@ViewBuilder
private func sessionRow(_ session: SessionInfoDto) -> some View {
ListRow {
ZStack {
session.device.clientColor

Image(session.device.image)
.resizable()
.aspectRatio(contentMode: .fit)
.padding(8)
}
.posterStyle(.square)
.frame(width: 60, height: 60)
.posterShadow()
} content: {
HStack {
VStack(alignment: .leading, spacing: 4) {

Text(session.userName ?? L10n.unknown)
.font(.headline)

if let client = session.client {
LabeledContent(
L10n.client,
value: client
)
}

if let device = session.deviceName {
LabeledContent(
L10n.device,
value: device
)
}
}
.font(.subheadline)

Spacer()

ListRowCheckbox()
}
.isEditing(true)
.isSelected(isCasting(to: session))
} action: {
toggleCast(to: session)
}
.isSeparatorVisible(false)
}
}
27 changes: 27 additions & 0 deletions Shared/Components/RemotePlayback/RemotePlaybackSessionView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2026 Jellyfin & Jellyfin Contributors
//

import SwiftUI

struct RemotePlaybackSessionView: View {

let route: RemotePlaybackRoute
let deviceName: String?

var body: some View {
ZStack {
Color.black

ContentUnavailableView(
L10n.playingOnDevice(deviceName ?? route.displayTitle),
systemImage: route.systemImage
)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
Loading