This commit is contained in:
Aiden Vigue 2021-06-21 02:41:45 -04:00
parent a7ed08e2b5
commit b27e8c6a74
No known key found for this signature in database
GPG Key ID: B9A09843AB079D5B
7 changed files with 77 additions and 70 deletions

View File

@ -47,48 +47,48 @@ class DeviceProfileBuilder {
// Build direct play profiles // Build direct play profiles
var directPlayProfiles: [DirectPlayProfile] = [] 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) // Device supports Dolby Digital (AC3, EAC3)
if supportsFeature(minimumSupported: .A8X) { if supportsFeature(minimumSupported: .A8X) {
if supportsFeature(minimumSupported: .A9) { 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 { } 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? // Device supports Dolby Vision?
if supportsFeature(minimumSupported: .A10X) { 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? // Device supports Dolby Atmos?
if supportsFeature(minimumSupported: .A12) { 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 // Build transcoding profiles
var transcodingProfiles: [TranscodingProfile] = [] 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) // Device supports Dolby Digital (AC3, EAC3)
if supportsFeature(minimumSupported: .A8X) { if supportsFeature(minimumSupported: .A8X) {
if supportsFeature(minimumSupported: .A9) { 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 { } 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? // Device supports Dolby Vision?
if supportsFeature(minimumSupported: .A10X) { 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? // Device supports Dolby Atmos?
if supportsFeature(minimumSupported: .A12) { 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] = [] var codecProfiles: [CodecProfile] = []
@ -96,12 +96,12 @@ class DeviceProfileBuilder {
let h264CodecConditions: [ProfileCondition] = [ let h264CodecConditions: [ProfileCondition] = [
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false), ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline", 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)] ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
let hevcCodecConditions: [ProfileCondition] = [ let hevcCodecConditions: [ProfileCondition] = [
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false), ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "main|main 10", isRequired: false), ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|main 10", isRequired: false),
ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "160", isRequired: false), ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "175", isRequired: false),
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)] ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264")) 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: "ssa", method: .embed))
subtitleProfiles.append(SubtitleProfile(format: "subrip", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "subrip", method: .embed))
subtitleProfiles.append(SubtitleProfile(format: "sub", 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: "subrip", method: .external))
subtitleProfiles.append(SubtitleProfile(format: "sub", 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: "ass", method: .external))
subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .external))
subtitleProfiles.append(SubtitleProfile(format: "vtt", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "vtt", method: .external))

View File

@ -34,7 +34,7 @@ struct ItemView: View {
.statusBar(hidden: true) .statusBar(hidden: true)
.edgesIgnoringSafeArea(.all) .edgesIgnoringSafeArea(.all)
.prefersHomeIndicatorAutoHidden(true) .prefersHomeIndicatorAutoHidden(true)
}.supportedOrientations(.landscape), isActive: $videoPlayerItem.shouldShowPlayer) { }, isActive: $videoPlayerItem.shouldShowPlayer) {
EmptyView() EmptyView()
} }
VStack { VStack {

View File

@ -12,7 +12,6 @@ import SwiftUI
struct LibrarySearchView: View { struct LibrarySearchView: View {
@StateObject var viewModel: LibrarySearchViewModel @StateObject var viewModel: LibrarySearchViewModel
@State var searchQuery = "" @State var searchQuery = ""
// MARK: tracks for grid
@State private var tracks: [GridItem] = Array(repeating: .init(.flexible()), count: Int(UIScreen.main.bounds.size.width) / 125) @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) Spacer().frame(height: 6)
SearchBar(text: $searchQuery) SearchBar(text: $searchQuery)
ZStack { ZStack {
ScrollView(.vertical) { if(!viewModel.isLoading) {
if !viewModel.items.isEmpty { ScrollView(.vertical) {
Spacer().frame(height: 16) if !viewModel.items.isEmpty {
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) 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 :(")
} }
} } else {
if viewModel.isLoading {
ProgressView() ProgressView()
} }
} }

View File

@ -53,6 +53,9 @@ struct LibraryView: View {
.fontWeight(.medium) .fontWeight(.medium)
} else { } else {
Text(item.type ?? "") Text(item.type ?? "")
.foregroundColor(.secondary)
.font(.caption)
.fontWeight(.medium)
} }
}.frame(width: 100) }.frame(width: 100)
} }
@ -69,7 +72,7 @@ struct LibraryView: View {
} label: { } label: {
Image(systemName: "chevron.left") Image(systemName: "chevron.left")
.font(.system(size: 25)) .font(.system(size: 25))
}.disabled(viewModel.hasPreviousPage) }.disabled(!viewModel.hasPreviousPage)
Text("Page \(String(viewModel.currentPage + 1)) of \(String(viewModel.totalPages))") Text("Page \(String(viewModel.currentPage + 1)) of \(String(viewModel.totalPages))")
.font(.headline) .font(.headline)
.fontWeight(.semibold) .fontWeight(.semibold)
@ -78,7 +81,7 @@ struct LibraryView: View {
} label: { } label: {
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.font(.system(size: 25)) .font(.system(size: 25))
}.disabled(viewModel.hasNextPage) }.disabled(!viewModel.hasNextPage)
} }
Spacer() Spacer()
} }

View File

@ -22,7 +22,7 @@ struct SettingsView: View {
func onAppear() { func onAppear() {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
username = SessionManager.current.user.username! username = SessionManager.current.user.username ?? ""
inNetworkStreamBitrate = defaults.integer(forKey: "InNetworkBandwidth") inNetworkStreamBitrate = defaults.integer(forKey: "InNetworkBandwidth")
outOfNetworkStreamBitrate = defaults.integer(forKey: "OutOfNetworkBandwidth") outOfNetworkStreamBitrate = defaults.integer(forKey: "OutOfNetworkBandwidth")
autoSelectSubtitles = defaults.bool(forKey: "AutoSelectSubtitles") autoSelectSubtitles = defaults.bool(forKey: "AutoSelectSubtitles")

View File

@ -523,7 +523,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
} else { } else {
deliveryUrl = nil 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 { if subtitle.delivery != .encode {
subtitleTrackArray.append(subtitle) subtitleTrackArray.append(subtitle)
@ -548,6 +548,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
self.sendPlayReport() self.sendPlayReport()
playbackItem = item playbackItem = item
} }
dump(playbackItem)
startLocalPlaybackEngine(true) startLocalPlaybackEngine(true)
}) })
.store(in: &cancellables) .store(in: &cancellables)
@ -583,29 +584,22 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe
} }
if(fetchCaptions) { if(fetchCaptions) {
print("Fetching captions.")
// Pause and load captions into memory. // Pause and load captions into memory.
mediaPlayer.pause() mediaPlayer.pause()
var shouldHaveSubtitleTracks = 1
subtitleTrackArray.forEach { sub in subtitleTrackArray.forEach { sub in
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" { if sub.id != -1 && sub.delivery == .external {
shouldHaveSubtitleTracks = shouldHaveSubtitleTracks + 1
mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false) 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() self.mediaHasStartedPlaying()
delegate?.hideLoadingView(self) delegate?.hideLoadingView(self)
mediaPlayer.pause()
mediaPlayer.play()
print("Local engine started.") print("Local engine started.")
} }
@ -837,6 +831,7 @@ extension PlayerViewController: VLCMediaPlayerDelegate {
} }
if CACurrentMediaTime() - lastProgressReportTime > 5 { if CACurrentMediaTime() - lastProgressReportTime > 5 {
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
sendProgressReport(eventName: "timeupdate") sendProgressReport(eventName: "timeupdate")
lastProgressReportTime = CACurrentMediaTime() lastProgressReportTime = CACurrentMediaTime()
} }

View File

@ -72,7 +72,20 @@ final class ConnectToServerViewModel: ViewModel {
func login() { func login() {
SessionManager.current.login(username: usernameSubject.value, password: passwordSubject.value) SessionManager.current.login(username: usernameSubject.value, password: passwordSubject.value)
.sink(receiveCompletion: { completion in .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 }, receiveValue: { _ in
}) })