mirror of
https://github.com/yattee/yattee.git
synced 2024-12-15 06:40:32 +05:30
parent
169a48e5f0
commit
2d5e34594a
@ -488,8 +488,14 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
}
|
||||
|
||||
private func extractStreams(from json: JSON) -> [Stream] {
|
||||
extractFormatStreams(from: json["formatStreams"].arrayValue) +
|
||||
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue)
|
||||
let hls = extractHLSStreams(from: json)
|
||||
if json["liveNow"].boolValue {
|
||||
return hls
|
||||
}
|
||||
|
||||
return extractFormatStreams(from: json["formatStreams"].arrayValue) +
|
||||
extractAdaptiveFormats(from: json["adaptiveFormats"].arrayValue) +
|
||||
hls
|
||||
}
|
||||
|
||||
private func extractFormatStreams(from streams: [JSON]) -> [Stream] {
|
||||
@ -538,6 +544,14 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
}
|
||||
}
|
||||
|
||||
private func extractHLSStreams(from content: JSON) -> [Stream] {
|
||||
if let hlsURL = content.dictionaryValue["hlsUrl"]?.url {
|
||||
return [Stream(hlsURL: hlsURL)]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
private func extractRelated(from content: JSON) -> [Video] {
|
||||
content
|
||||
.dictionaryValue["recommendedVideos"]?
|
||||
@ -576,8 +590,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
|
||||
private func extractCaptions(from content: JSON) -> [Captions] {
|
||||
content["captions"].arrayValue.compactMap { details in
|
||||
guard let baseURL = account.url,
|
||||
let url = URL(string: baseURL + details["url"].stringValue) else { return nil }
|
||||
let baseURL = account.url
|
||||
guard let url = URL(string: baseURL + details["url"].stringValue) else { return nil }
|
||||
|
||||
return Captions(
|
||||
label: details["label"].stringValue,
|
||||
|
@ -132,6 +132,8 @@ final class AVPlayerBackend: PlayerBackend {
|
||||
}
|
||||
|
||||
func seek(to time: CMTime, completionHandler: ((Bool) -> Void)?) {
|
||||
guard !model.live else { return }
|
||||
|
||||
avPlayer.seek(
|
||||
to: time,
|
||||
toleranceBefore: .secondsInDefaultTimescale(1),
|
||||
|
@ -140,6 +140,8 @@ final class MPVClient: ObservableObject {
|
||||
options.append("sub-files-append=\"\(subURL)\"")
|
||||
}
|
||||
|
||||
options.append("force-seekable=yes")
|
||||
|
||||
args.append(options.joined(separator: ","))
|
||||
|
||||
command("loadfile", args: args, returnValueCallback: completionHandler)
|
||||
|
@ -273,7 +273,7 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
|
||||
var videoDuration: TimeInterval? {
|
||||
currentItem?.duration ?? currentVideo?.length ?? playerItemDuration?.seconds
|
||||
playerItemDuration?.seconds ?? currentItem?.duration ?? currentVideo?.length
|
||||
}
|
||||
|
||||
var time: CMTime? {
|
||||
@ -284,6 +284,18 @@ final class PlayerModel: ObservableObject {
|
||||
currentVideo?.live ?? false
|
||||
}
|
||||
|
||||
var playingLive: Bool {
|
||||
guard live,
|
||||
let videoDuration = videoDuration,
|
||||
let time = backend.currentTime?.seconds else { return false }
|
||||
|
||||
return videoDuration - time < 30
|
||||
}
|
||||
|
||||
var liveStreamInAVPlayer: Bool {
|
||||
live && activeBackend == .appleAVPlayer
|
||||
}
|
||||
|
||||
func togglePlay() {
|
||||
backend.togglePlay()
|
||||
}
|
||||
@ -751,7 +763,7 @@ final class PlayerModel: ObservableObject {
|
||||
var nowPlayingInfo: [String: AnyObject] = [
|
||||
MPMediaItemPropertyTitle: video.title as AnyObject,
|
||||
MPMediaItemPropertyArtist: video.author as AnyObject,
|
||||
MPNowPlayingInfoPropertyIsLiveStream: video.live as AnyObject,
|
||||
MPNowPlayingInfoPropertyIsLiveStream: live as AnyObject,
|
||||
MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime as AnyObject,
|
||||
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
|
||||
MPNowPlayingInfoPropertyPlaybackQueueIndex: 1 as AnyObject,
|
||||
|
@ -54,6 +54,7 @@ struct ControlsOverlay: View {
|
||||
Text(backend.label)
|
||||
.padding(6)
|
||||
.foregroundColor(player.activeBackend == backend ? .accentColor : .secondary)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
@ -317,6 +317,7 @@ struct PlayerControls: View {
|
||||
button("Seek Backward", systemImage: "gobackward.10", size: 25, cornerRadius: 5, background: false) {
|
||||
player.backend.seek(relative: .secondsInDefaultTimescale(-10))
|
||||
}
|
||||
.disabled(player.liveStreamInAVPlayer)
|
||||
#if os(tvOS)
|
||||
.focused($focusedField, equals: .backward)
|
||||
#else
|
||||
@ -329,6 +330,7 @@ struct PlayerControls: View {
|
||||
button("Seek Forward", systemImage: "goforward.10", size: 25, cornerRadius: 5, background: false) {
|
||||
player.backend.seek(relative: .secondsInDefaultTimescale(10))
|
||||
}
|
||||
.disabled(player.liveStreamInAVPlayer)
|
||||
#if os(tvOS)
|
||||
.focused($focusedField, equals: .forward)
|
||||
#else
|
||||
|
@ -108,6 +108,7 @@ struct TimelineView: View {
|
||||
.animation(.easeOut, value: thumbTooltipOffset)
|
||||
HStack(spacing: 4) {
|
||||
Text((dragging ? projectedValue : nil)?.formattedAsPlaybackTime(allowZero: true) ?? playerTime.currentPlaybackTime)
|
||||
.opacity(player.liveStreamInAVPlayer ? 0 : 1)
|
||||
.frame(minWidth: 35)
|
||||
#if os(tvOS)
|
||||
.font(.system(size: 20))
|
||||
@ -190,7 +191,7 @@ struct TimelineView: View {
|
||||
)
|
||||
#endif
|
||||
}
|
||||
|
||||
.opacity(player.liveStreamInAVPlayer ? 0 : 1)
|
||||
.overlay(GeometryReader { proxy in
|
||||
Color.clear
|
||||
.onAppear {
|
||||
@ -209,6 +210,37 @@ struct TimelineView: View {
|
||||
})
|
||||
#endif
|
||||
|
||||
durationView
|
||||
.frame(minWidth: 30, alignment: .trailing)
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 3))
|
||||
.font(.system(size: 9).monospacedDigit())
|
||||
.zIndex(2)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder var durationView: some View {
|
||||
if player.live {
|
||||
if player.playingLive || player.activeBackend == .appleAVPlayer {
|
||||
Text("LIVE")
|
||||
.fontWeight(.bold)
|
||||
.padding(2)
|
||||
.foregroundColor(.white)
|
||||
.background(RoundedRectangle(cornerRadius: 2).foregroundColor(.red))
|
||||
} else {
|
||||
Button {
|
||||
if let duration = player.videoDuration {
|
||||
player.backend.seek(to: duration - 5)
|
||||
}
|
||||
} label: {
|
||||
Text("LIVE")
|
||||
.fontWeight(.bold)
|
||||
.padding(2)
|
||||
.foregroundColor(.primary)
|
||||
.background(RoundedRectangle(cornerRadius: 2).strokeBorder(.red, lineWidth: 1).foregroundColor(.white))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(dragging ? playerTime.durationPlaybackTime : playerTime.withoutSegmentsPlaybackTime)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 3))
|
||||
.frame(minWidth: 35)
|
||||
@ -216,10 +248,6 @@ struct TimelineView: View {
|
||||
.font(.system(size: 20))
|
||||
#endif
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: 3))
|
||||
.font(.system(size: 9).monospacedDigit())
|
||||
.zIndex(2)
|
||||
}
|
||||
}
|
||||
|
||||
var tooltipVeritcalOffset: Double {
|
@ -1450,6 +1450,7 @@
|
||||
37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */,
|
||||
37F13B61285E43C000B137E4 /* ControlsOverlay.swift */,
|
||||
37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */,
|
||||
37E8B0EB27B326C00024006F /* TimelineView.swift */,
|
||||
37648B68286CF5F1003D330B /* TVControls.swift */,
|
||||
37E80F3B287B107F00561799 /* VideoDetailsOverlay.swift */,
|
||||
);
|
||||
@ -1499,7 +1500,6 @@
|
||||
37B81AFB26D2C9C900675966 /* VideoDetailsPaddingModifier.swift */,
|
||||
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */,
|
||||
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */,
|
||||
37E8B0EB27B326C00024006F /* TimelineView.swift */,
|
||||
37F9619A27BD89E000058149 /* TapRecognizerViewModifier.swift */,
|
||||
373031F22838388A000CFD59 /* PlayerLayerView.swift */,
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user