playback
This commit is contained in:
parent
a7ed08e2b5
commit
b27e8c6a74
|
@ -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))
|
||||
|
|
|
@ -34,7 +34,7 @@ struct ItemView: View {
|
|||
.statusBar(hidden: true)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.prefersHomeIndicatorAutoHidden(true)
|
||||
}.supportedOrientations(.landscape), isActive: $videoPlayerItem.shouldShowPlayer) {
|
||||
}, isActive: $videoPlayerItem.shouldShowPlayer) {
|
||||
EmptyView()
|
||||
}
|
||||
VStack {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue