From b27e8c6a74812389031b8ba37546b4f805937fe1 Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Mon, 21 Jun 2021 02:41:45 -0400 Subject: [PATCH] playback --- JellyfinPlayer/DeviceProfileBuilder.swift | 30 ++++---- JellyfinPlayer/ItemView.swift | 2 +- JellyfinPlayer/LibrarySearchView.swift | 68 +++++++++---------- JellyfinPlayer/LibraryView.swift | 7 +- JellyfinPlayer/SettingsView.swift | 2 +- JellyfinPlayer/VideoPlayer.swift | 23 +++---- .../ViewModels/ConnectToServerViewModel.swift | 15 +++- 7 files changed, 77 insertions(+), 70 deletions(-) diff --git a/JellyfinPlayer/DeviceProfileBuilder.swift b/JellyfinPlayer/DeviceProfileBuilder.swift index d4aed011..bc81d402 100644 --- a/JellyfinPlayer/DeviceProfileBuilder.swift +++ b/JellyfinPlayer/DeviceProfileBuilder.swift @@ -47,48 +47,48 @@ class DeviceProfileBuilder { // Build direct play profiles var directPlayProfiles: [DirectPlayProfile] = [] - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav", videoCodec: "h264", type: .video)] + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav", videoCodec: "h264,mpeg4", type: .video)] // Device supports Dolby Digital (AC3, EAC3) if supportsFeature(minimumSupported: .A8X) { if supportsFeature(minimumSupported: .A9) { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1", type: .video)] // HEVC/H.264 with Dolby Digital + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1,mpeg4", type: .video)] // HEVC/H.264 with Dolby Digital } else { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264", type: .video)] // H.264 with Dolby Digital + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264,mpeg4", type: .video)] // H.264 with Dolby Digital } } // Device supports Dolby Vision? if supportsFeature(minimumSupported: .A10X) { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,h264,hevc,hev1", type: .video)] // H.264/HEVC with Dolby Digital - No Atmos - Vision + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,h264,hevc,hev1,mpeg4", type: .video)] // H.264/HEVC with Dolby Digital - No Atmos - Vision } // Device supports Dolby Atmos? if supportsFeature(minimumSupported: .A12) { - directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,h264,hevc,hev1", type: .video)] // H.264/HEVC with Dolby Digital & Atmos - Vision + directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,h264,hevc,hev1,mpeg4", type: .video)] // H.264/HEVC with Dolby Digital & Atmos - Vision } // Build transcoding profiles var transcodingProfiles: [TranscodingProfile] = [] - transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav")] + transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,mpeg4", audioCodec: "aac,mp3,wav")] // Device supports Dolby Digital (AC3, EAC3) if supportsFeature(minimumSupported: .A8X) { if supportsFeature(minimumSupported: .A9) { - transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,hevc,hev1", audioCodec: "aac,mp3,wav,eac3,ac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] + transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,hevc,hev1,mpeg4", audioCodec: "aac,mp3,wav,eac3,ac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } else { - transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] + transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,mpeg4", audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } } // Device supports Dolby Vision? if supportsFeature(minimumSupported: .A10X) { - transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] + transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dvhe,dvh1,hevc,h264,hev1,mpeg4", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } // Device supports Dolby Atmos? if supportsFeature(minimumSupported: .A12) { - transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,dts,truehd,dca,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] + transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dvhe,dvh1,hevc,h264,hev1,mpeg4", audioCodec: "aac,mp3,wav,ac3,eac3,flac,dts,truehd,dca,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } var codecProfiles: [CodecProfile] = [] @@ -96,12 +96,12 @@ class DeviceProfileBuilder { let h264CodecConditions: [ProfileCondition] = [ ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false), ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline", isRequired: false), - ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "60", isRequired: false), + ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "80", isRequired: false), ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)] let hevcCodecConditions: [ProfileCondition] = [ ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false), - ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "main|main 10", isRequired: false), - ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "160", isRequired: false), + ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|main 10", isRequired: false), + ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "175", isRequired: false), ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)] codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264")) @@ -116,12 +116,8 @@ class DeviceProfileBuilder { subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "subrip", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "sub", method: .embed)) - subtitleProfiles.append(SubtitleProfile(format: "pgssub", method: .embed)) - subtitleProfiles.append(SubtitleProfile(format: "pgs", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "subrip", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "sub", method: .external)) - subtitleProfiles.append(SubtitleProfile(format: "pgssub", method: .external)) - subtitleProfiles.append(SubtitleProfile(format: "pgs", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "ass", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "vtt", method: .external)) diff --git a/JellyfinPlayer/ItemView.swift b/JellyfinPlayer/ItemView.swift index 2eb8f9a2..a60937cc 100644 --- a/JellyfinPlayer/ItemView.swift +++ b/JellyfinPlayer/ItemView.swift @@ -34,7 +34,7 @@ struct ItemView: View { .statusBar(hidden: true) .edgesIgnoringSafeArea(.all) .prefersHomeIndicatorAutoHidden(true) - }.supportedOrientations(.landscape), isActive: $videoPlayerItem.shouldShowPlayer) { + }, isActive: $videoPlayerItem.shouldShowPlayer) { EmptyView() } VStack { diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 465c48f0..bf086554 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -12,7 +12,6 @@ import SwiftUI struct LibrarySearchView: View { @StateObject var viewModel: LibrarySearchViewModel @State var searchQuery = "" - // MARK: tracks for grid @State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) @@ -25,42 +24,43 @@ struct LibrarySearchView: View { Spacer().frame(height: 6) SearchBar(text: $searchQuery) ZStack { - ScrollView(.vertical) { - if !viewModel.items.isEmpty { - Spacer().frame(height: 16) - LazyVGrid(columns: tracks) { - ForEach(viewModel.items, id: \.id) { item in - NavigationLink(destination: ItemView(item: item)) { - VStack(alignment: .leading) { - ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) - .frame(width: 100, height: 150) - .cornerRadius(10) - Text(item.name ?? "") - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.primary) - .lineLimit(1) - if item.productionYear != nil { - Text(String(item.productionYear!)) - .foregroundColor(.secondary) - .font(.caption) - .fontWeight(.medium) - } else { - Text(item.type ?? "") - } - }.frame(width: 100) - } - } + if(!viewModel.isLoading) { + ScrollView(.vertical) { + if !viewModel.items.isEmpty { Spacer().frame(height: 16) + LazyVGrid(columns: tracks) { + ForEach(viewModel.items, id: \.id) { item in + NavigationLink(destination: ItemView(item: item)) { + VStack(alignment: .leading) { + ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) + .frame(width: 100, height: 150) + .cornerRadius(10) + Text(item.name ?? "") + .font(.caption) + .fontWeight(.semibold) + .foregroundColor(.primary) + .lineLimit(1) + if item.productionYear != nil { + Text(String(item.productionYear!)) + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else { + Text(item.type ?? "") + } + }.frame(width: 100) + } + } + Spacer().frame(height: 16) + } + .onRotate { _ in + recalcTracks() + } + } else { + Text("Query returned 0 results.") } - .onRotate { _ in - recalcTracks() - } - } else if !viewModel.isLoading { - Text("No results :(") } - } - if viewModel.isLoading { + } else { ProgressView() } } diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index c9673c46..164d8300 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -53,6 +53,9 @@ struct LibraryView: View { .fontWeight(.medium) } else { Text(item.type ?? "") + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) } }.frame(width: 100) } @@ -69,7 +72,7 @@ struct LibraryView: View { } label: { Image(systemName: "chevron.left") .font(.system(size: 25)) - }.disabled(viewModel.hasPreviousPage) + }.disabled(!viewModel.hasPreviousPage) Text("Page \(String(viewModel.currentPage + 1)) of \(String(viewModel.totalPages))") .font(.headline) .fontWeight(.semibold) @@ -78,7 +81,7 @@ struct LibraryView: View { } label: { Image(systemName: "chevron.right") .font(.system(size: 25)) - }.disabled(viewModel.hasNextPage) + }.disabled(!viewModel.hasNextPage) } Spacer() } diff --git a/JellyfinPlayer/SettingsView.swift b/JellyfinPlayer/SettingsView.swift index 5abb6ab2..0af6b472 100644 --- a/JellyfinPlayer/SettingsView.swift +++ b/JellyfinPlayer/SettingsView.swift @@ -22,7 +22,7 @@ struct SettingsView: View { func onAppear() { let defaults = UserDefaults.standard - username = SessionManager.current.user.username! + username = SessionManager.current.user.username ?? "" inNetworkStreamBitrate = defaults.integer(forKey: "InNetworkBandwidth") outOfNetworkStreamBitrate = defaults.integer(forKey: "OutOfNetworkBandwidth") autoSelectSubtitles = defaults.bool(forKey: "AutoSelectSubtitles") diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index 11640305..73137cd0 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -523,7 +523,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe } else { deliveryUrl = nil } - let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec ?? "webvtt") + let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) if subtitle.delivery != .encode { subtitleTrackArray.append(subtitle) @@ -548,6 +548,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe self.sendPlayReport() playbackItem = item } + dump(playbackItem) startLocalPlaybackEngine(true) }) .store(in: &cancellables) @@ -583,29 +584,22 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe } if(fetchCaptions) { + print("Fetching captions.") // Pause and load captions into memory. mediaPlayer.pause() - var shouldHaveSubtitleTracks = 1 subtitleTrackArray.forEach { sub in - if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" { - shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1 + if sub.id != -1 && sub.delivery == .external { mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false) } } - - // Wait for captions to load - delegate?.showLoadingView(self) - - while mediaPlayer.numberOfSubtitlesTracks != shouldHaveSubtitleTracks {} - - // Select default track & resume playback - mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack - mediaPlayer.pause() - mediaPlayer.play() } self.mediaHasStartedPlaying() delegate?.hideLoadingView(self) + + mediaPlayer.pause() + mediaPlayer.play() + print("Local engine started.") } @@ -837,6 +831,7 @@ extension PlayerViewController: VLCMediaPlayerDelegate { } if CACurrentMediaTime() - lastProgressReportTime > 5 { + mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack sendProgressReport(eventName: "timeupdate") lastProgressReportTime = CACurrentMediaTime() } diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index d652ccec..a726ce0c 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -72,7 +72,20 @@ final class ConnectToServerViewModel: ViewModel { func login() { SessionManager.current.login(username: usernameSubject.value, password: passwordSubject.value) .sink(receiveCompletion: { completion in - self.handleAPIRequestCompletion(completion: completion) + switch completion { + case .finished: + break + case .failure(let error): + if let err = error as? ErrorResponse { + switch err { + case .error(401, _, _, _): + self.errorMessage = "Invalid credentials" + case .error: + self.errorMessage = err.localizedDescription + } + } + break + } }, receiveValue: { _ in })