mirror of
https://github.com/yattee/yattee.git
synced 2025-04-25 08:06:31 +00:00
Merge pull request #667 from stonerl/hls-set-target-quality
HLS: set target bitrate / AVPlayer: higher resolution
This commit is contained in:
commit
c6724472a6
@ -654,7 +654,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
resolution: Stream.Resolution.from(resolution: videoStream["resolution"].stringValue),
|
||||
kind: .adaptive,
|
||||
encoding: videoStream["encoding"].string,
|
||||
videoFormat: videoStream["type"].string
|
||||
videoFormat: videoStream["type"].string,
|
||||
bitrate: videoStream["bitrate"].int
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -687,6 +687,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
let fps = qualityComponents.count > 1 ? Int(qualityComponents[1]) : 30
|
||||
let resolution = Stream.Resolution.from(resolution: quality, fps: fps)
|
||||
let videoFormat = videoStream.dictionaryValue["format"]?.string
|
||||
let bitrate = videoStream.dictionaryValue["bitrate"]?.int
|
||||
|
||||
if videoOnly {
|
||||
streams.append(
|
||||
@ -696,7 +697,8 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
videoAsset: videoAsset,
|
||||
resolution: resolution,
|
||||
kind: .adaptive,
|
||||
videoFormat: videoFormat
|
||||
videoFormat: videoFormat,
|
||||
bitrate: bitrate
|
||||
)
|
||||
)
|
||||
} else {
|
||||
|
@ -307,6 +307,11 @@ final class AVPlayerBackend: PlayerBackend {
|
||||
removeItemDidPlayToEndTimeObserver()
|
||||
|
||||
model.playerItem = playerItem(stream)
|
||||
|
||||
if stream.isHLS {
|
||||
model.playerItem?.preferredPeakBitRate = Double(model.qualityProfile?.resolution.value.bitrate ?? 0)
|
||||
}
|
||||
|
||||
guard model.playerItem != nil else {
|
||||
return
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ final class MPVBackend: PlayerBackend {
|
||||
}
|
||||
}
|
||||
|
||||
client.loadFile(url, sub: captions?.url, time: time, forceSeekable: stream.kind == .hls) { [weak self] _ in
|
||||
client.loadFile(url, bitrate: stream.bitrate, kind: stream.kind, sub: captions?.url, time: time, forceSeekable: stream.kind == .hls) { [weak self] _ in
|
||||
self?.isLoadingVideo = true
|
||||
}
|
||||
} else {
|
||||
@ -313,7 +313,7 @@ final class MPVBackend: PlayerBackend {
|
||||
let fileToLoad = self.model.musicMode ? stream.audioAsset.url : stream.videoAsset.url
|
||||
let audioTrack = self.model.musicMode ? nil : stream.audioAsset.url
|
||||
|
||||
client.loadFile(fileToLoad, audio: audioTrack, sub: captions?.url, time: time, forceSeekable: stream.kind == .hls) { [weak self] _ in
|
||||
client.loadFile(fileToLoad, audio: audioTrack, bitrate: stream.bitrate, kind: stream.kind, sub: captions?.url, time: time, forceSeekable: stream.kind == .hls) { [weak self] _ in
|
||||
self?.isLoadingVideo = true
|
||||
self?.pause()
|
||||
}
|
||||
|
@ -128,6 +128,8 @@ final class MPVClient: ObservableObject {
|
||||
func loadFile(
|
||||
_ url: URL,
|
||||
audio: URL? = nil,
|
||||
bitrate: Int? = nil,
|
||||
kind: Stream.Kind,
|
||||
sub: URL? = nil,
|
||||
time: CMTime? = nil,
|
||||
forceSeekable: Bool = false,
|
||||
@ -160,6 +162,10 @@ final class MPVClient: ObservableObject {
|
||||
args.append(options.joined(separator: ","))
|
||||
}
|
||||
|
||||
if kind == .hls, bitrate != 0 {
|
||||
checkError(mpv_set_option_string(mpv, "hls-bitrate", String(describing: bitrate)))
|
||||
}
|
||||
|
||||
command("loadfile", args: args, returnValueCallback: completionHandler)
|
||||
}
|
||||
|
||||
|
@ -137,11 +137,17 @@ extension PlayerBackend {
|
||||
// find max resolution from non HLS streams
|
||||
let bestResolution = nonHLSStreams
|
||||
.filter { $0.resolution <= maxResolution.value }
|
||||
.max { $0.resolution < $1.resolution }?.resolution
|
||||
.max { $0.resolution < $1.resolution }
|
||||
|
||||
// finde max bitrate from non HLS streams
|
||||
let bestBitrate = nonHLSStreams
|
||||
.filter { $0.resolution <= maxResolution.value }
|
||||
.max { $0.bitrate ?? 0 < $1.bitrate ?? 0 }
|
||||
|
||||
return streams.map { stream in
|
||||
if stream.kind == .hls {
|
||||
stream.resolution = bestResolution ?? maxResolution.value
|
||||
stream.resolution = bestResolution?.resolution ?? maxResolution.value
|
||||
stream.bitrate = bestBitrate?.bitrate ?? (bestResolution?.resolution.bitrate ?? maxResolution.value.bitrate)
|
||||
stream.format = .hls
|
||||
} else if stream.kind == .stream {
|
||||
stream.format = .stream
|
||||
|
@ -54,6 +54,32 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
return Int(refreshRatePart.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? -1
|
||||
}
|
||||
|
||||
// These values are an approximation.
|
||||
// https://support.google.com/youtube/answer/1722171?hl=en#zippy=%2Cbitrate
|
||||
|
||||
var bitrate: Int {
|
||||
switch self {
|
||||
case .hd2160p60, .hd2160p50, .hd2160p48, .hd2160p30:
|
||||
return 56000000 // 56 Mbit/s
|
||||
case .hd1440p60, .hd1440p50, .hd1440p48, .hd1440p30:
|
||||
return 24000000 // 24 Mbit/s
|
||||
case .hd1080p60, .hd1080p50, .hd1080p48, .hd1080p30:
|
||||
return 12000000 // 12 Mbit/s
|
||||
case .hd720p60, .hd720p50, .hd720p48, .hd720p30:
|
||||
return 9500000 // 9.5 Mbit/s
|
||||
case .sd480p30:
|
||||
return 4000000 // 4 Mbit/s
|
||||
case .sd360p30:
|
||||
return 1500000 // 1.5 Mbit/s
|
||||
case .sd240p30:
|
||||
return 1000000 // 1 Mbit/s
|
||||
case .sd144p30:
|
||||
return 600000 // 0.6 Mbit/s
|
||||
case .unknown:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
static func from(resolution: String, fps: Int? = nil) -> Self {
|
||||
allCases.first { $0.rawValue.contains(resolution) && $0.refreshRate == (fps ?? 30) } ?? .unknown
|
||||
}
|
||||
@ -143,6 +169,7 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
|
||||
var encoding: String?
|
||||
var videoFormat: String?
|
||||
var bitrate: Int?
|
||||
|
||||
init(
|
||||
instance: Instance? = nil,
|
||||
@ -153,7 +180,8 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
resolution: Resolution? = nil,
|
||||
kind: Kind = .hls,
|
||||
encoding: String? = nil,
|
||||
videoFormat: String? = nil
|
||||
videoFormat: String? = nil,
|
||||
bitrate: Int? = nil
|
||||
) {
|
||||
self.instance = instance
|
||||
self.audioAsset = audioAsset
|
||||
@ -164,6 +192,7 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
self.kind = kind
|
||||
self.encoding = encoding
|
||||
format = .from(videoFormat ?? "")
|
||||
self.bitrate = bitrate
|
||||
}
|
||||
|
||||
var isLocal: Bool {
|
||||
|
@ -5,6 +5,23 @@ import SwiftUI
|
||||
enum Constants {
|
||||
static let yatteeProtocol = "yattee://"
|
||||
static let overlayAnimation = Animation.linear(duration: 0.2)
|
||||
|
||||
static var isAppleTV: Bool {
|
||||
#if os(iOS)
|
||||
UIDevice.current.userInterfaceIdiom == .tv
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
|
||||
static var isMac: Bool {
|
||||
#if os(iOS)
|
||||
UIDevice.current.userInterfaceIdiom == .mac
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
}
|
||||
|
||||
static var isIPhone: Bool {
|
||||
#if os(iOS)
|
||||
UIDevice.current.userInterfaceIdiom == .phone
|
||||
|
@ -140,7 +140,11 @@ struct QualityProfileForm: View {
|
||||
Text("Formats can be reordered and will be selected in this order.")
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
Text("**Note:** HLS is an adaptive format, resolution setting doesn't apply.")
|
||||
Text("**Note:** HLS is an adaptive format where specific resolution settings don't apply.")
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top)
|
||||
Text("Yattee attempts to match the quality that is closest to the set resolution, but exact results cannot be guaranteed.")
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top, 0.1)
|
||||
@ -301,7 +305,7 @@ struct QualityProfileForm: View {
|
||||
func isResolutionDisabled(_ resolution: ResolutionSetting) -> Bool {
|
||||
guard backend == .appleAVPlayer else { return false }
|
||||
|
||||
return resolution.value > .hd720p30
|
||||
return resolution.value > .hd1080p60
|
||||
}
|
||||
|
||||
func initializeForm() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user