mirror of
https://github.com/yattee/yattee.git
synced 2025-12-12 19:18:16 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a194738bb6 | ||
|
|
45567254f2 | ||
|
|
a3139ad059 | ||
|
|
598f17479f | ||
|
|
e888abfba9 | ||
|
|
e1e53b2d36 | ||
|
|
9510d91d61 | ||
|
|
59da0e71b6 | ||
|
|
6bdfb7368c | ||
|
|
7b26fdf400 | ||
|
|
67b41e36d5 | ||
|
|
c9c60349df | ||
|
|
6a70663f06 | ||
|
|
3d556d836f | ||
|
|
8feeb33a55 | ||
|
|
497c3bfc12 | ||
|
|
b51eadc7a9 | ||
|
|
7d0c1180c4 | ||
|
|
0c1fb02d50 | ||
|
|
6f358fab56 | ||
|
|
7631e2a8ed | ||
|
|
3a5f3fdfde | ||
|
|
e3633bdaf7 | ||
|
|
e912d910bc | ||
|
|
5ccb0f90d5 | ||
|
|
278bc343c2 | ||
|
|
5dc197664d | ||
|
|
192550ba7a | ||
|
|
3369e23e74 | ||
|
|
4381511c91 | ||
|
|
af99df9b8a | ||
|
|
21f21cc944 | ||
|
|
e1d8bb8125 | ||
|
|
d948ea6887 | ||
|
|
66eb8051bf | ||
|
|
95d3170d31 | ||
|
|
74b6adb247 | ||
|
|
a45522f710 | ||
|
|
0b01adf6eb | ||
|
|
444f6bcc03 | ||
|
|
3f871bce2c |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,14 +1,10 @@
|
||||
## Build 188
|
||||
* Improved thumbnail handling by @stonerl in https://github.com/yattee/yattee/pull/740
|
||||
* iOS: make timestamps in comments touchable by @stonerl in https://github.com/yattee/yattee/pull/741
|
||||
* Improvements to opening channels from Videos by @stonerl in https://github.com/yattee/yattee/pull/742
|
||||
* Allow hiding comments by @stonerl in https://github.com/yattee/yattee/pull/744
|
||||
* Add option to exit fullscreen on end by @stonerl in https://github.com/yattee/yattee/pull/570
|
||||
* Only updateWatch status while video is playing by @stonerl in https://github.com/yattee/yattee/pull/745
|
||||
* Xcode 16 - update recommended settings by @stonerl in https://github.com/yattee/yattee/pull/737
|
||||
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/724
|
||||
* Updated dependencies
|
||||
* Other minor changes and improvements
|
||||
## Build 192
|
||||
* Fix mpv crashing on macOS by @stonerl in https://github.com/yattee/yattee/pull/754
|
||||
* Refreshed icons for iOS and macOS by @stonerl in https://github.com/yattee/yattee/pull/752
|
||||
* Add new MPVKit repo by @stonerl in https://github.com/yattee/yattee/pull/753
|
||||
* Add Chinese (Simplified) - zh-Hans to LanguageCodes by @stonerl in https://github.com/yattee/yattee/pull/757
|
||||
* Color changes to VideoActions by @stonerl in https://github.com/yattee/yattee/pull/759
|
||||
* Hide VideoActions Bar when no buttons is visible by @stonerl in https://github.com/yattee/yattee/pull/760
|
||||
|
||||
## Previous builds
|
||||
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
|
||||
@@ -27,6 +23,18 @@
|
||||
* Add import export of missing settings
|
||||
* macOS: Fix settings windows layout
|
||||
* Fix seek OSD layout on tvOS, revert OSD position
|
||||
* Improved stream resolution handling by @stonerl in https://github.com/yattee/yattee/pull/747
|
||||
* Fix some potential crashes by @stonerl in https://github.com/yattee/yattee/pull/748
|
||||
* Fix regression and improve curentChapter handling by @stonerl in https://github.com/yattee/yattee/pull/749
|
||||
* Refined chapter font scaling by @stonerl in https://github.com/yattee/yattee/pull/750
|
||||
* Improved thumbnail handling by @stonerl in https://github.com/yattee/yattee/pull/740
|
||||
* iOS: make timestamps in comments touchable by @stonerl in https://github.com/yattee/yattee/pull/741
|
||||
* Improvements to opening channels from Videos by @stonerl in https://github.com/yattee/yattee/pull/742
|
||||
* Allow hiding comments by @stonerl in https://github.com/yattee/yattee/pull/744
|
||||
* Add option to exit fullscreen on end by @stonerl in https://github.com/yattee/yattee/pull/570
|
||||
* Only updateWatch status while video is playing by @stonerl in https://github.com/yattee/yattee/pull/745
|
||||
* Xcode 16 - update recommended settings by @stonerl in https://github.com/yattee/yattee/pull/737
|
||||
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/724
|
||||
* tvOS: Allow account picker by long pressing channels button in subscriptions view by @patelhiren in https://github.com/yattee/yattee/pull/704
|
||||
* tvOS: Refined Subscriptions View by @patelhiren in https://github.com/yattee/yattee/pull/697
|
||||
* More responsive UI when Favorites are used. by @stonerl in https://github.com/yattee/yattee/pull/695
|
||||
|
||||
@@ -3,7 +3,7 @@ import Foundation
|
||||
|
||||
final class MenuModel: ObservableObject {
|
||||
static let shared = MenuModel()
|
||||
private var cancellables = [AnyCancellable]()
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init() {
|
||||
registerChildModel(AccountsModel.shared)
|
||||
@@ -12,10 +12,16 @@ final class MenuModel: ObservableObject {
|
||||
}
|
||||
|
||||
func registerChildModel<T: ObservableObject>(_ model: T?) {
|
||||
guard !model.isNil else {
|
||||
guard let model else {
|
||||
return
|
||||
}
|
||||
|
||||
cancellables.append(model!.objectWillChange.sink { [weak self] _ in self?.objectWillChange.send() })
|
||||
model.objectWillChange
|
||||
.receive(on: DispatchQueue.main) // Ensure the update occurs on the main thread
|
||||
.debounce(for: .milliseconds(10), scheduler: DispatchQueue.main) // Debounce to avoid immediate feedback loops
|
||||
.sink { [weak self] _ in
|
||||
self?.objectWillChange.send()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import AVFAudio
|
||||
import CoreMedia
|
||||
import Defaults
|
||||
import Foundation
|
||||
import Libmpv
|
||||
import Logging
|
||||
import MediaPlayer
|
||||
import MPVKit
|
||||
import Repeat
|
||||
import SwiftUI
|
||||
|
||||
@@ -360,8 +360,8 @@ final class MPVBackend: PlayerBackend {
|
||||
setRate(model.currentRate)
|
||||
|
||||
// After the video has ended, hitting play restarts the video from the beginning.
|
||||
if currentTime?.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime() &&
|
||||
currentTime!.seconds > 0 && model.playerTime.duration.seconds > 0
|
||||
if let currentTime, currentTime.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime() &&
|
||||
currentTime.seconds > 0 && model.playerTime.duration.seconds > 0
|
||||
{
|
||||
seek(to: 0, seekType: .loopRestart)
|
||||
}
|
||||
@@ -464,6 +464,8 @@ final class MPVBackend: PlayerBackend {
|
||||
timeObserverThrottle.execute {
|
||||
self.model.updateWatch(time: self.currentTime)
|
||||
}
|
||||
|
||||
self.model.updateTime(self.currentTime!)
|
||||
}
|
||||
|
||||
private func stopClientUpdates() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import CoreMedia
|
||||
import Defaults
|
||||
import Foundation
|
||||
import Libmpv
|
||||
import Logging
|
||||
import MPVKit
|
||||
#if !os(macOS)
|
||||
import Siesta
|
||||
import UIKit
|
||||
@@ -99,6 +99,11 @@ final class MPVClient: ObservableObject {
|
||||
checkError(mpv_set_option_string(mpv, "demuxer-lavf-analyzeduration", "1"))
|
||||
checkError(mpv_set_option_string(mpv, "demuxer-lavf-probe-info", Defaults[.mpvDemuxerLavfProbeInfo]))
|
||||
|
||||
// Disable ytdl, since it causes crashes on macOS.
|
||||
#if os(macOS)
|
||||
checkError(mpv_set_option_string(mpv, "ytdl", "no"))
|
||||
#endif
|
||||
|
||||
checkError(mpv_initialize(mpv))
|
||||
|
||||
let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import CoreMedia
|
||||
import Defaults
|
||||
import Foundation
|
||||
import Logging
|
||||
#if !os(macOS)
|
||||
import UIKit
|
||||
#endif
|
||||
@@ -75,6 +76,10 @@ protocol PlayerBackend {
|
||||
}
|
||||
|
||||
extension PlayerBackend {
|
||||
var logger: Logger {
|
||||
return Logger(label: "stream.yattee.player.backend")
|
||||
}
|
||||
|
||||
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||
model.seek.registerSeek(at: time, type: seekType, restore: currentTime)
|
||||
seek(to: time, seekType: seekType, completionHandler: completionHandler)
|
||||
@@ -140,55 +145,87 @@ extension PlayerBackend {
|
||||
}
|
||||
|
||||
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting, formatOrder: [QualityProfile.Format]) -> Stream? {
|
||||
// filter out non-HLS streams and streams with resolution more than maxResolution
|
||||
let nonHLSStreams = streams.filter {
|
||||
$0.kind != .hls && $0.resolution <= maxResolution.value
|
||||
}
|
||||
logger.info("Starting bestPlayable function")
|
||||
logger.info("Total streams received: \(streams.count)")
|
||||
logger.info("Max resolution allowed: \(String(describing: maxResolution.value))")
|
||||
logger.info("Format order: \(formatOrder)")
|
||||
|
||||
// find max resolution and bitrate from non-HLS streams
|
||||
// Filter out non-HLS streams and streams with resolution more than maxResolution
|
||||
let nonHLSStreams = streams.filter {
|
||||
let isHLS = $0.kind == .hls
|
||||
let isWithinResolution = $0.resolution <= maxResolution.value
|
||||
logger.info("Stream ID: \($0.id) - Kind: \(String(describing: $0.kind)) - Resolution: \(String(describing: $0.resolution)) - Bitrate: \($0.bitrate ?? 0)")
|
||||
logger.info("Is HLS: \(isHLS), Is within resolution: \(isWithinResolution)")
|
||||
return !isHLS && isWithinResolution
|
||||
}
|
||||
logger.info("Non-HLS streams after filtering: \(nonHLSStreams.count)")
|
||||
|
||||
// Find max resolution and bitrate from non-HLS streams
|
||||
let bestResolutionStream = nonHLSStreams.max { $0.resolution < $1.resolution }
|
||||
let bestBitrateStream = nonHLSStreams.max { $0.bitrate ?? 0 < $1.bitrate ?? 0 }
|
||||
|
||||
logger.info("Best resolution stream: \(String(describing: bestResolutionStream?.id)) with resolution: \(String(describing: bestResolutionStream?.resolution))")
|
||||
logger.info("Best bitrate stream: \(String(describing: bestBitrateStream?.id)) with bitrate: \(String(describing: bestBitrateStream?.bitrate))")
|
||||
|
||||
let bestResolution = bestResolutionStream?.resolution ?? maxResolution.value
|
||||
let bestBitrate = bestBitrateStream?.bitrate ?? bestResolutionStream?.resolution.bitrate ?? maxResolution.value.bitrate
|
||||
|
||||
return streams.map { stream in
|
||||
logger.info("Final best resolution selected: \(String(describing: bestResolution))")
|
||||
logger.info("Final best bitrate selected: \(bestBitrate)")
|
||||
|
||||
let adjustedStreams = streams.map { stream in
|
||||
if stream.kind == .hls {
|
||||
logger.info("Adjusting HLS stream ID: \(stream.id)")
|
||||
stream.resolution = bestResolution
|
||||
stream.bitrate = bestBitrate
|
||||
stream.format = .hls
|
||||
} else if stream.kind == .stream {
|
||||
logger.info("Adjusting non-HLS stream ID: \(stream.id)")
|
||||
stream.format = .stream
|
||||
}
|
||||
return stream
|
||||
}
|
||||
.filter { stream in
|
||||
stream.resolution <= maxResolution.value
|
||||
|
||||
let filteredStreams = adjustedStreams.filter { stream in
|
||||
let isWithinResolution = stream.resolution <= maxResolution.value
|
||||
logger.info("Filtered stream ID: \(stream.id) - Is within max resolution: \(isWithinResolution)")
|
||||
return isWithinResolution
|
||||
}
|
||||
.max { lhs, rhs in
|
||||
|
||||
logger.info("Filtered streams count after adjustments: \(filteredStreams.count)")
|
||||
|
||||
let bestStream = filteredStreams.max { lhs, rhs in
|
||||
if lhs.resolution == rhs.resolution {
|
||||
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
|
||||
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
|
||||
else {
|
||||
print("Failed to extract lhsFormat or rhsFormat")
|
||||
logger.info("Failed to extract lhsFormat or rhsFormat for streams \(lhs.id) and \(rhs.id)")
|
||||
return false
|
||||
}
|
||||
|
||||
let lhsFormatIndex = formatOrder.firstIndex(of: lhsFormat) ?? Int.max
|
||||
let rhsFormatIndex = formatOrder.firstIndex(of: rhsFormat) ?? Int.max
|
||||
|
||||
logger.info("Comparing formats for streams \(lhs.id) and \(rhs.id) - LHS Format Index: \(lhsFormatIndex), RHS Format Index: \(rhsFormatIndex)")
|
||||
|
||||
return lhsFormatIndex > rhsFormatIndex
|
||||
}
|
||||
|
||||
logger.info("Comparing resolutions for streams \(lhs.id) and \(rhs.id) - LHS Resolution: \(String(describing: lhs.resolution)), RHS Resolution: \(String(describing: rhs.resolution))")
|
||||
|
||||
return lhs.resolution < rhs.resolution
|
||||
}
|
||||
|
||||
logger.info("Best stream selected: \(String(describing: bestStream?.id)) with resolution: \(String(describing: bestStream?.resolution)) and format: \(String(describing: bestStream?.format))")
|
||||
|
||||
return bestStream
|
||||
}
|
||||
|
||||
func updateControls(completionHandler: (() -> Void)? = nil) {
|
||||
print("updating controls")
|
||||
logger.info("updating controls")
|
||||
|
||||
guard model.presentingPlayer, !model.controls.presentingOverlays else {
|
||||
print("ignored controls update")
|
||||
logger.info("ignored controls update")
|
||||
completionHandler?()
|
||||
return
|
||||
}
|
||||
@@ -196,7 +233,7 @@ extension PlayerBackend {
|
||||
DispatchQueue.main.async(qos: .userInteractive) {
|
||||
#if !os(macOS)
|
||||
guard UIApplication.shared.applicationState != .background else {
|
||||
print("not performing controls updates in background")
|
||||
logger.info("not performing controls updates in background")
|
||||
completionHandler?()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ extension PlayerModel {
|
||||
var streamByQualityProfile: Stream? {
|
||||
let profile = qualityProfile ?? .defaultProfile
|
||||
|
||||
// First attempt: Filter by both `canPlay` and `isPreferred`
|
||||
if let streamPreferredForProfile = backend.bestPlayable(
|
||||
availableStreams.filter { backend.canPlay($0) && profile.isPreferred($0) },
|
||||
maxResolution: profile.resolution, formatOrder: profile.formats
|
||||
@@ -134,7 +135,24 @@ extension PlayerModel {
|
||||
return streamPreferredForProfile
|
||||
}
|
||||
|
||||
return backend.bestPlayable(availableStreams.filter { backend.canPlay($0) }, maxResolution: profile.resolution, formatOrder: profile.formats)
|
||||
// Fallback: Filter by `canPlay` only
|
||||
let fallbackStream = backend.bestPlayable(
|
||||
availableStreams.filter { backend.canPlay($0) },
|
||||
maxResolution: profile.resolution, formatOrder: profile.formats
|
||||
)
|
||||
|
||||
// If no stream is found, trigger the error handler
|
||||
guard let finalStream = fallbackStream else {
|
||||
let error = RequestError(
|
||||
userMessage: "No supported streams available.",
|
||||
cause: NSError(domain: "stream.yatte.app", code: -1, userInfo: [NSLocalizedDescriptionKey: "No supported streams available"])
|
||||
)
|
||||
videoLoadFailureHandler(error, video: currentVideo)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the found stream
|
||||
return finalStream
|
||||
}
|
||||
|
||||
func advanceToNextItem() {
|
||||
|
||||
@@ -69,7 +69,10 @@ final class PlaylistsModel: ObservableObject {
|
||||
.onSuccess { resource in
|
||||
self.error = nil
|
||||
if let playlists: [Playlist] = resource.typedContent() {
|
||||
self.playlists = playlists
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self else { return }
|
||||
self.playlists = playlists
|
||||
}
|
||||
PlaylistsCacheModel.shared.storePlaylist(account: account, playlists: playlists)
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
@@ -5,26 +5,153 @@ import Foundation
|
||||
// swiftlint:disable:next final_class
|
||||
class Stream: Equatable, Hashable, Identifiable {
|
||||
enum Resolution: String, CaseIterable, Comparable, Defaults.Serializable {
|
||||
// Some 16:19 and 16:10 resolutions are also used in 2:1 videos
|
||||
|
||||
// 8K UHD (16:9) Resolutions
|
||||
case hd4320p60
|
||||
case hd4320p50
|
||||
case hd4320p48
|
||||
case hd4320p30
|
||||
case hd4320p25
|
||||
case hd4320p24
|
||||
|
||||
// 5K (16:9) Resolutions
|
||||
case hd2560p60
|
||||
case hd2560p50
|
||||
case hd2560p48
|
||||
case hd2560p30
|
||||
case hd2560p25
|
||||
case hd2560p24
|
||||
|
||||
// 2:1 Aspect Ratio (Univisium) Resolutions
|
||||
case hd2880p60
|
||||
case hd2880p50
|
||||
case hd2880p48
|
||||
case hd2880p30
|
||||
case hd2880p25
|
||||
case hd2880p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd2400p60
|
||||
case hd2400p50
|
||||
case hd2400p48
|
||||
case hd2400p30
|
||||
case hd2400p25
|
||||
case hd2400p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd2160p60
|
||||
case hd2160p50
|
||||
case hd2160p48
|
||||
case hd2160p30
|
||||
case hd2160p25
|
||||
case hd2160p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1600p60
|
||||
case hd1600p50
|
||||
case hd1600p48
|
||||
case hd1600p30
|
||||
case hd1600p25
|
||||
case hd1600p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd1440p60
|
||||
case hd1440p50
|
||||
case hd1440p48
|
||||
case hd1440p30
|
||||
case hd1440p25
|
||||
case hd1440p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1280p60
|
||||
case hd1280p50
|
||||
case hd1280p48
|
||||
case hd1280p30
|
||||
case hd1280p25
|
||||
case hd1280p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1200p60
|
||||
case hd1200p50
|
||||
case hd1200p48
|
||||
case hd1200p30
|
||||
case hd1200p25
|
||||
case hd1200p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd1080p60
|
||||
case hd1080p50
|
||||
case hd1080p48
|
||||
case hd1080p30
|
||||
case hd1080p25
|
||||
case hd1080p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd1050p60
|
||||
case hd1050p50
|
||||
case hd1050p48
|
||||
case hd1050p30
|
||||
case hd1050p25
|
||||
case hd1050p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd960p60
|
||||
case hd960p50
|
||||
case hd960p48
|
||||
case hd960p30
|
||||
case hd960p25
|
||||
case hd960p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd900p60
|
||||
case hd900p50
|
||||
case hd900p48
|
||||
case hd900p30
|
||||
case hd900p25
|
||||
case hd900p24
|
||||
|
||||
// 16:10 Resolutions
|
||||
case hd800p60
|
||||
case hd800p50
|
||||
case hd800p48
|
||||
case hd800p30
|
||||
case hd800p25
|
||||
case hd800p24
|
||||
|
||||
// 16:9 Resolutions
|
||||
case hd720p60
|
||||
case hd720p50
|
||||
case hd720p48
|
||||
case hd720p30
|
||||
case hd720p25
|
||||
case hd720p24
|
||||
|
||||
// Standard Definition (SD) Resolutions
|
||||
case sd854p30
|
||||
case sd854p25
|
||||
case sd768p30
|
||||
case sd768p25
|
||||
case sd640p30
|
||||
case sd640p25
|
||||
case sd480p30
|
||||
case sd480p25
|
||||
|
||||
case sd428p30
|
||||
case sd428p25
|
||||
case sd360p30
|
||||
case sd360p25
|
||||
case sd320p30
|
||||
case sd320p25
|
||||
case sd240p30
|
||||
case sd240p25
|
||||
case sd214p30
|
||||
case sd214p25
|
||||
case sd144p30
|
||||
case sd144p25
|
||||
case sd128p30
|
||||
case sd128p25
|
||||
|
||||
case unknown
|
||||
|
||||
var name: String {
|
||||
@@ -59,22 +186,94 @@ class Stream: Equatable, Hashable, Identifiable {
|
||||
|
||||
var bitrate: Int {
|
||||
switch self {
|
||||
case .hd2160p60, .hd2160p50, .hd2160p48, .hd2160p30:
|
||||
// 8K UHD (16:9) Resolutions
|
||||
case .hd4320p60, .hd4320p50, .hd4320p48, .hd4320p30, .hd4320p25, .hd4320p24:
|
||||
return 85_000_000 // 85 Mbit/s
|
||||
|
||||
// 5K (16:9) Resolutions
|
||||
case .hd2880p60, .hd2880p50, .hd2880p48, .hd2880p30, .hd2880p25, .hd2880p24:
|
||||
return 45_000_000 // 45 Mbit/s
|
||||
|
||||
// 2:1 Aspect Ratio (Univisium) Resolutions
|
||||
case .hd2560p60, .hd2560p50, .hd2560p48, .hd2560p30, .hd2560p25, .hd2560p24:
|
||||
return 30_000_000 // 30 Mbit/s
|
||||
|
||||
// 16:10 Resolutions
|
||||
case .hd2400p60, .hd2400p50, .hd2400p48, .hd2400p30, .hd2400p25, .hd2400p24:
|
||||
return 35_000_000 // 35 Mbit/s
|
||||
|
||||
// 4K UHD (16:9) Resolutions
|
||||
case .hd2160p60, .hd2160p50, .hd2160p48, .hd2160p30, .hd2160p25, .hd2160p24:
|
||||
return 56_000_000 // 56 Mbit/s
|
||||
case .hd1440p60, .hd1440p50, .hd1440p48, .hd1440p30:
|
||||
|
||||
// 16:10 Resolutions
|
||||
case .hd1600p60, .hd1600p50, .hd1600p48, .hd1600p30, .hd1600p25, .hd1600p24:
|
||||
return 20_000_000 // 20 Mbit/s
|
||||
|
||||
// 1440p (16:9) Resolutions
|
||||
case .hd1440p60, .hd1440p50, .hd1440p48, .hd1440p30, .hd1440p25, .hd1440p24:
|
||||
return 24_000_000 // 24 Mbit/s
|
||||
case .hd1080p60, .hd1080p50, .hd1080p48, .hd1080p30:
|
||||
|
||||
// 1280p (16:10) Resolutions
|
||||
case .hd1280p60, .hd1280p50, .hd1280p48, .hd1280p30, .hd1280p25, .hd1280p24:
|
||||
return 15_000_000 // 15 Mbit/s
|
||||
|
||||
// 1200p (16:10) Resolutions
|
||||
case .hd1200p60, .hd1200p50, .hd1200p48, .hd1200p30, .hd1200p25, .hd1200p24:
|
||||
return 18_000_000 // 18 Mbit/s
|
||||
|
||||
// 1080p (16:9) Resolutions
|
||||
case .hd1080p60, .hd1080p50, .hd1080p48, .hd1080p30, .hd1080p25, .hd1080p24:
|
||||
return 12_000_000 // 12 Mbit/s
|
||||
case .hd720p60, .hd720p50, .hd720p48, .hd720p30:
|
||||
|
||||
// 1050p (16:10) Resolutions
|
||||
case .hd1050p60, .hd1050p50, .hd1050p48, .hd1050p30, .hd1050p25, .hd1050p24:
|
||||
return 10_000_000 // 10 Mbit/s
|
||||
|
||||
// 960p Resolutions
|
||||
case .hd960p60, .hd960p50, .hd960p48, .hd960p30, .hd960p25, .hd960p24:
|
||||
return 8_000_000 // 8 Mbit/s
|
||||
|
||||
// 900p (16:10) Resolutions
|
||||
case .hd900p60, .hd900p50, .hd900p48, .hd900p30, .hd900p25, .hd900p24:
|
||||
return 7_000_000 // 7 Mbit/s
|
||||
|
||||
// 800p (16:10) Resolutions
|
||||
case .hd800p60, .hd800p50, .hd800p48, .hd800p30, .hd800p25, .hd800p24:
|
||||
return 6_000_000 // 6 Mbit/s
|
||||
|
||||
// 720p (16:9) Resolutions
|
||||
case .hd720p60, .hd720p50, .hd720p48, .hd720p30, .hd720p25, .hd720p24:
|
||||
return 9_500_000 // 9.5 Mbit/s
|
||||
case .sd480p30:
|
||||
|
||||
// Standard Definition (SD) Resolutions
|
||||
case .sd854p30, .sd854p25, .sd768p30, .sd768p25, .sd640p30, .sd640p25:
|
||||
return 4_000_000 // 4 Mbit/s
|
||||
case .sd360p30:
|
||||
|
||||
case .sd480p30, .sd480p25:
|
||||
return 2_500_000 // 2.5 Mbit/s
|
||||
|
||||
case .sd428p30, .sd428p25:
|
||||
return 2_000_000 // 2 Mbit/s
|
||||
|
||||
case .sd360p30, .sd360p25:
|
||||
return 1_500_000 // 1.5 Mbit/s
|
||||
case .sd240p30:
|
||||
|
||||
case .sd320p30, .sd320p25:
|
||||
return 1_200_000 // 1.2 Mbit/s
|
||||
|
||||
case .sd240p30, .sd240p25:
|
||||
return 1_000_000 // 1 Mbit/s
|
||||
case .sd144p30:
|
||||
|
||||
case .sd214p30, .sd214p25:
|
||||
return 800_000 // 0.8 Mbit/s
|
||||
|
||||
case .sd144p30, .sd144p25:
|
||||
return 600_000 // 0.6 Mbit/s
|
||||
|
||||
case .sd128p30, .sd128p25:
|
||||
return 400_000 // 0.4 Mbit/s
|
||||
|
||||
case .unknown:
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ enum LanguageCodes: String, CaseIterable {
|
||||
case Vietnamese = "vi"
|
||||
case Xhosa = "xh"
|
||||
case Chinese = "zh"
|
||||
case Chinese_Hans = "zh-Hans"
|
||||
case Zulu = "zu"
|
||||
|
||||
var description: String {
|
||||
@@ -147,6 +148,8 @@ enum LanguageCodes: String, CaseIterable {
|
||||
return "Xhosa"
|
||||
case .Chinese:
|
||||
return "Chinese"
|
||||
case .Chinese_Hans:
|
||||
return "Chinese (Simplified)"
|
||||
case .Zulu:
|
||||
return "Zulu"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import GLKit
|
||||
import Libmpv
|
||||
import Logging
|
||||
import MPVKit
|
||||
import OpenGLES
|
||||
|
||||
final class MPVOGLView: GLKView {
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
import AVKit
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
final class PlayerViewController: UIViewController {
|
||||
var playerLoaded = false
|
||||
var commentsModel: CommentsModel!
|
||||
var navigationModel: NavigationModel!
|
||||
var playerModel: PlayerModel!
|
||||
var subscriptionsModel: SubscriptionsModel!
|
||||
var playerView = AVPlayerViewController()
|
||||
|
||||
let persistenceController = PersistenceController.shared
|
||||
|
||||
#if !os(tvOS)
|
||||
var aspectRatio: Double? {
|
||||
let ratio = Double(playerView.videoBounds.width) / Double(playerView.videoBounds.height)
|
||||
|
||||
guard ratio.isFinite else {
|
||||
return VideoPlayerView.defaultAspectRatio // swiftlint:disable:this implicit_return
|
||||
}
|
||||
|
||||
return [ratio, 1.0].max()!
|
||||
}
|
||||
#endif
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
loadPlayer()
|
||||
|
||||
#if os(tvOS)
|
||||
if !playerView.isBeingPresented, !playerView.isBeingDismissed {
|
||||
present(playerView, animated: false)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
if !playerModel.presentingPlayer, !Defaults[.pauseOnHidingPlayer], !playerModel.isPlaying {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
||||
self?.playerModel.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
func loadPlayer() {
|
||||
guard !playerLoaded else {
|
||||
return
|
||||
}
|
||||
|
||||
playerModel.controller = self
|
||||
playerView.player = playerModel.player
|
||||
playerView.allowsPictureInPicturePlayback = true
|
||||
#if os(iOS)
|
||||
if #available(iOS 14.2, *) {
|
||||
playerView.canStartPictureInPictureAutomaticallyFromInline = true
|
||||
}
|
||||
#endif
|
||||
playerView.delegate = self
|
||||
|
||||
#if os(tvOS)
|
||||
var infoViewControllers = [UIHostingController<AnyView>]()
|
||||
if CommentsModel.enabled {
|
||||
infoViewControllers.append(infoViewController([.comments], title: "Comments"))
|
||||
}
|
||||
|
||||
var queueSections = [NowPlayingView.ViewSection.playingNext]
|
||||
if Defaults[.showHistoryInPlayer] {
|
||||
queueSections.append(.playedPreviously)
|
||||
}
|
||||
|
||||
infoViewControllers.append(contentsOf: [
|
||||
infoViewController([.related], title: "Related"),
|
||||
infoViewController(queueSections, title: "Queue")
|
||||
])
|
||||
|
||||
playerView.customInfoViewControllers = infoViewControllers
|
||||
#else
|
||||
embedViewController()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
func infoViewController(
|
||||
_ sections: [NowPlayingView.ViewSection],
|
||||
title: String
|
||||
) -> UIHostingController<AnyView> {
|
||||
let controller = UIHostingController(
|
||||
rootView:
|
||||
AnyV/Users/arek/Developer/Yattee/Shared/Player/PlayerViewController.swift.iew(
|
||||
NowPlayingView(sections: sections, inInfoViewController: true)
|
||||
.frame(maxHeight: 600)
|
||||
.environmentObject(commentsModel)
|
||||
.environmentObject(playerModel)
|
||||
.environmentObject(subscriptionsModel)
|
||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||
)
|
||||
)
|
||||
|
||||
controller.title = title
|
||||
|
||||
return controller
|
||||
}
|
||||
#else
|
||||
func embedViewController() {
|
||||
playerView.view.frame = view.bounds
|
||||
|
||||
addChild(playerView)
|
||||
view.addSubview(playerView.view)
|
||||
|
||||
playerView.didMove(toParent: self)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extension PlayerViewController: AVPlayerViewControllerDelegate {
|
||||
func playerViewControllerShouldDismiss(_: AVPlayerViewController) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart(_: AVPlayerViewController) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func playerViewControllerWillBeginDismissalTransition(_: AVPlayerViewController) {
|
||||
if Defaults[.pauseOnHidingPlayer] {
|
||||
playerModel.pause()
|
||||
}
|
||||
dismiss(animated: false)
|
||||
}
|
||||
|
||||
func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) {}
|
||||
|
||||
func playerViewController(
|
||||
_: AVPlayerViewController,
|
||||
willBeginFullScreenPresentationWithAnimationCoordinator context: UIViewControllerTransitionCoordinator
|
||||
) {
|
||||
playerModel.playingFullscreen = true
|
||||
|
||||
#if os(iOS)
|
||||
if !context.isCancelled, Defaults[.lockLandscapeWhenEnteringFullscreen] {
|
||||
Orientation.lockOrientation(.landscape, andRotateTo: UIDevice.current.orientation.isLandscape ? nil : .landscapeRight)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func playerViewController(
|
||||
_: AVPlayerViewController,
|
||||
willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator
|
||||
) {
|
||||
let wasPlaying = playerModel.isPlaying
|
||||
coordinator.animate(alongsideTransition: nil) { context in
|
||||
#if os(iOS)
|
||||
if wasPlaying {
|
||||
self.playerModel.play()
|
||||
}
|
||||
#endif
|
||||
if !context.isCancelled {
|
||||
#if os(iOS)
|
||||
self.playerModel.lockedOrientation = nil
|
||||
if Defaults[.enterFullscreenInLandscape] {
|
||||
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
|
||||
}
|
||||
|
||||
self.playerModel.playingFullscreen = false
|
||||
|
||||
if wasPlaying {
|
||||
self.playerModel.play()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func playerViewController(
|
||||
_: AVPlayerViewController,
|
||||
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
|
||||
) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
if self.navigationModel.presentingChannel {
|
||||
self.playerModel.playerNavigationLinkActive = true
|
||||
} else {
|
||||
self.playerModel.show()
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
if self.playerModel.playingInPictureInPicture {
|
||||
self.present(self.playerView, animated: false) {
|
||||
completionHandler(true)
|
||||
}
|
||||
}
|
||||
#else
|
||||
completionHandler(true)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {
|
||||
playerModel.playingInPictureInPicture = true
|
||||
playerModel.playerNavigationLinkActive = false
|
||||
}
|
||||
|
||||
func playerViewControllerWillStopPictureInPicture(_: AVPlayerViewController) {
|
||||
playerModel.playingInPictureInPicture = false
|
||||
}
|
||||
}
|
||||
@@ -29,18 +29,14 @@ struct ChaptersView: View {
|
||||
ScrollView(.horizontal) {
|
||||
ScrollViewReader { scrollViewProxy in
|
||||
LazyHStack(spacing: 20) {
|
||||
chapterViews(for: chapters[...], scrollViewProxy: scrollViewProxy)
|
||||
chapterViews(for: chapters[...])
|
||||
}
|
||||
.padding(.horizontal, 15)
|
||||
.onAppear {
|
||||
if let currentChapterIndex = player.currentChapterIndex {
|
||||
scrollViewProxy.scrollTo(currentChapterIndex, anchor: .center)
|
||||
}
|
||||
scrollToCurrentChapter(scrollViewProxy)
|
||||
}
|
||||
.onChange(of: player.currentChapterIndex) { currentChapterIndex in
|
||||
if let index = currentChapterIndex {
|
||||
scrollViewProxy.scrollTo(index, anchor: .center)
|
||||
}
|
||||
.onChange(of: player.currentChapterIndex) { _ in
|
||||
scrollToCurrentChapter(scrollViewProxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +49,8 @@ struct ChaptersView: View {
|
||||
}
|
||||
}
|
||||
#else
|
||||
Section { chapterViews(for: chapters[...]) }.padding(.horizontal)
|
||||
Section { chapterViews(for: chapters[...]) }
|
||||
.padding(.horizontal)
|
||||
#endif
|
||||
} else {
|
||||
#if os(iOS)
|
||||
@@ -80,7 +77,7 @@ struct ChaptersView: View {
|
||||
}
|
||||
|
||||
#if !os(tvOS)
|
||||
private func chapterViews(for chaptersToShow: ArraySlice<Chapter>, opacity: Double = 1.0, clickable: Bool = true, scrollViewProxy _: ScrollViewProxy? = nil) -> some View {
|
||||
private func chapterViews(for chaptersToShow: ArraySlice<Chapter>, opacity: Double = 1.0, clickable: Bool = true) -> some View {
|
||||
ForEach(Array(chaptersToShow.indices), id: \.self) { index in
|
||||
let chapter = chaptersToShow[index]
|
||||
ChapterView(chapter: chapter, chapterIndex: index, showThumbnail: showThumbnails)
|
||||
@@ -89,6 +86,14 @@ struct ChaptersView: View {
|
||||
.allowsHitTesting(clickable)
|
||||
}
|
||||
}
|
||||
|
||||
private func scrollToCurrentChapter(_ scrollViewProxy: ScrollViewProxy) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // Slight delay to ensure the view is fully rendered
|
||||
if let currentChapterIndex = player.currentChapterIndex {
|
||||
scrollViewProxy.scrollTo(currentChapterIndex, anchor: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,10 @@ struct VideoActions: View {
|
||||
}
|
||||
}
|
||||
|
||||
func isAnyActionVisible() -> Bool {
|
||||
return Action.allCases.contains { isVisible($0) }
|
||||
}
|
||||
|
||||
func isActionable(_ action: Action) -> Bool {
|
||||
switch action {
|
||||
case .share:
|
||||
@@ -198,10 +202,10 @@ struct VideoActions: View {
|
||||
VStack(spacing: 3) {
|
||||
Image(systemName: systemImage)
|
||||
.frame(width: 20, height: 20)
|
||||
.foregroundColor(active ? Color("AppRedColor") : .accentColor)
|
||||
.foregroundColor(active ? Color("AppRedColor") : .primary)
|
||||
if playerActionsButtonLabelStyle.text {
|
||||
Text(name.localized())
|
||||
.foregroundColor(active ? Color("AppRedColor") : .secondary)
|
||||
.foregroundColor(active ? Color("AppRedColor") : .primary)
|
||||
.font(.caption2)
|
||||
.allowsTightening(true)
|
||||
.lineLimit(1)
|
||||
|
||||
@@ -235,15 +235,22 @@ struct VideoDetails: View {
|
||||
)
|
||||
#endif
|
||||
// swiftlint:enable trailing_closure
|
||||
|
||||
VideoActions(video: player.videoForDisplay)
|
||||
.padding(.vertical, 5)
|
||||
.frame(maxHeight: 50)
|
||||
.frame(maxWidth: .infinity)
|
||||
.borderTop(height: 0.5, color: Color("ControlsBorderColor"))
|
||||
.borderBottom(height: 0.5, color: Color("ControlsBorderColor"))
|
||||
.animation(nil, value: player.currentItem)
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
if VideoActions().isAnyActionVisible() {
|
||||
VideoActions(video: player.videoForDisplay)
|
||||
.padding(.vertical, 5)
|
||||
.frame(maxHeight: 50)
|
||||
.frame(maxWidth: .infinity)
|
||||
.borderTop(height: 0.5, color: Color("ControlsBorderColor"))
|
||||
.borderBottom(height: 0.5, color: Color("ControlsBorderColor"))
|
||||
.animation(nil, value: player.currentItem)
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
} else {
|
||||
Rectangle()
|
||||
.fill(Color.clear)
|
||||
.frame(height: 0.5)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color("ControlsBorderColor"))
|
||||
}
|
||||
|
||||
ScrollViewReader { proxy in
|
||||
pageView
|
||||
|
||||
@@ -361,9 +361,9 @@ struct PlayerSettings: View {
|
||||
|
||||
private var captionsFontScaleSizePicker: some View {
|
||||
Picker("Size", selection: $captionsFontScaleSize) {
|
||||
Text("Small").tag(String("0.5"))
|
||||
Text("Small").tag(String("0.725"))
|
||||
Text("Medium").tag(String("1.0"))
|
||||
Text("Large").tag(String("2.0"))
|
||||
Text("Large").tag(String("1.5"))
|
||||
}
|
||||
.onChange(of: captionsFontScaleSize) { _ in
|
||||
PlayerModel.shared.mpvBackend.client.setSubFontSize(scaleSize: captionsFontScaleSize)
|
||||
|
||||
@@ -191,7 +191,7 @@ struct YatteeApp: App {
|
||||
|
||||
NavigationModel.shared.tabSelection = section ?? .search
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
DispatchQueue.main.async {
|
||||
playlists.load()
|
||||
}
|
||||
|
||||
|
||||
@@ -619,9 +619,6 @@
|
||||
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104828D3D10600D5F53C /* SDWebImageSwiftUI */; };
|
||||
3797104B28D3D18800D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104A28D3D18800D5F53C /* SDWebImageSwiftUI */; };
|
||||
3797104D28D3D19100D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104C28D3D19100D5F53C /* SDWebImageSwiftUI */; };
|
||||
3797665B2C79FA6900C10DBD /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3797665A2C79FA6900C10DBD /* MPVKit */; };
|
||||
3797665D2C79FA7500C10DBD /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3797665C2C79FA7500C10DBD /* MPVKit */; };
|
||||
3797665F2C79FA7D00C10DBD /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3797665E2C79FA7D00C10DBD /* MPVKit */; };
|
||||
3797757D268922D100DD52A8 /* Siesta in Frameworks */ = {isa = PBXBuildFile; productRef = 3797757C268922D100DD52A8 /* Siesta */; };
|
||||
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; };
|
||||
37977584268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; };
|
||||
@@ -1080,6 +1077,9 @@
|
||||
E258F38A2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
|
||||
E258F38B2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
|
||||
E258F38C2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
|
||||
E265D0C22C7D217000D2BB8E /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = E265D0C12C7D217000D2BB8E /* MPVKit */; };
|
||||
E265D0C42C7D218A00D2BB8E /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = E265D0C32C7D218A00D2BB8E /* MPVKit */; };
|
||||
E265D0C62C7D21A300D2BB8E /* MPVKit in Frameworks */ = {isa = PBXBuildFile; productRef = E265D0C52C7D21A300D2BB8E /* MPVKit */; };
|
||||
E27568B92BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
|
||||
E27568BA2BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
|
||||
E27568BB2BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
|
||||
@@ -1599,7 +1599,7 @@
|
||||
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */,
|
||||
37BD07B92698AB2E003EBB87 /* Siesta in Frameworks */,
|
||||
37FB284D2722099E00A57617 /* SDWebImageWebPCoder in Frameworks */,
|
||||
3797665B2C79FA6900C10DBD /* MPVKit in Frameworks */,
|
||||
E265D0C22C7D217000D2BB8E /* MPVKit in Frameworks */,
|
||||
37CF8B8428535E4F00B71E37 /* SDWebImage in Frameworks */,
|
||||
37C7367A2AC33010007630E1 /* SwiftUIIntrospect in Frameworks */,
|
||||
);
|
||||
@@ -1624,7 +1624,7 @@
|
||||
375B8AB728B583BD00397B31 /* KeychainAccess in Frameworks */,
|
||||
3703205E27D2BB12007A0CB8 /* SDWebImageWebPCoder in Frameworks */,
|
||||
37CF8B8628535E5A00B71E37 /* SDWebImage in Frameworks */,
|
||||
3797665D2C79FA7500C10DBD /* MPVKit in Frameworks */,
|
||||
E265D0C42C7D218A00D2BB8E /* MPVKit in Frameworks */,
|
||||
3703205C27D2BAF3007A0CB8 /* SwiftyJSON in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1670,7 +1670,7 @@
|
||||
372915E42687E33E00F5A35B /* Defaults in Frameworks */,
|
||||
3772003B27E8EEC800CB2475 /* libbz2.tbd in Frameworks */,
|
||||
37BADCA9269A570B009BE4FB /* Alamofire in Frameworks */,
|
||||
3797665F2C79FA7D00C10DBD /* MPVKit in Frameworks */,
|
||||
E265D0C62C7D21A300D2BB8E /* MPVKit in Frameworks */,
|
||||
37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */,
|
||||
3797757D268922D100DD52A8 /* Siesta in Frameworks */,
|
||||
);
|
||||
@@ -2585,7 +2585,7 @@
|
||||
371AC0AB294D1A490085989E /* CachedAsyncImage */,
|
||||
379325D429A265A300181CF1 /* Logging */,
|
||||
37C736792AC33010007630E1 /* SwiftUIIntrospect */,
|
||||
3797665A2C79FA6900C10DBD /* MPVKit */,
|
||||
E265D0C12C7D217000D2BB8E /* MPVKit */,
|
||||
);
|
||||
productName = "Yattee (iOS)";
|
||||
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
|
||||
@@ -2624,7 +2624,7 @@
|
||||
371AC0B1294D1C230085989E /* CachedAsyncImage */,
|
||||
379325D629A265AE00181CF1 /* Logging */,
|
||||
37C736772AC32B28007630E1 /* SwiftUIIntrospect */,
|
||||
3797665C2C79FA7500C10DBD /* MPVKit */,
|
||||
E265D0C32C7D218A00D2BB8E /* MPVKit */,
|
||||
);
|
||||
productName = "Yattee (macOS)";
|
||||
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
|
||||
@@ -2702,7 +2702,7 @@
|
||||
377F9F75294403880043F856 /* Cache */,
|
||||
371AC0B3294D1C290085989E /* CachedAsyncImage */,
|
||||
379325D829A265B500181CF1 /* Logging */,
|
||||
3797665E2C79FA7D00C10DBD /* MPVKit */,
|
||||
E265D0C52C7D21A300D2BB8E /* MPVKit */,
|
||||
);
|
||||
productName = Yattee;
|
||||
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
|
||||
@@ -2822,7 +2822,7 @@
|
||||
374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */,
|
||||
371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */,
|
||||
379325D329A265A300181CF1 /* XCRemoteSwiftPackageReference "swift-log" */,
|
||||
379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */,
|
||||
E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */,
|
||||
);
|
||||
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -4103,7 +4103,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
|
||||
@@ -4134,7 +4134,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||
@@ -4165,7 +4165,7 @@
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
@@ -4185,7 +4185,7 @@
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
@@ -4348,7 +4348,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
@@ -4400,7 +4400,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
|
||||
@@ -4452,7 +4452,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@@ -4491,7 +4491,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
@@ -4525,7 +4525,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4548,7 +4548,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4573,7 +4573,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4597,7 +4597,7 @@
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
@@ -4623,7 +4623,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -4663,7 +4663,7 @@
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -4703,7 +4703,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4726,7 +4726,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 188;
|
||||
CURRENT_PROJECT_VERSION = 192;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4960,14 +4960,6 @@
|
||||
minimumVersion = 2.1.0;
|
||||
};
|
||||
};
|
||||
379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/cxfksword/MPVKit";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.38.0;
|
||||
};
|
||||
};
|
||||
3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/bustoutsolutions/siesta";
|
||||
@@ -5040,6 +5032,14 @@
|
||||
minimumVersion = 0.3.0;
|
||||
};
|
||||
};
|
||||
E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/mpvkit/MPVKit.git";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = "0.38.0-fix";
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@@ -5228,21 +5228,6 @@
|
||||
package = 3797104728D3D10600D5F53C /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */;
|
||||
productName = SDWebImageSwiftUI;
|
||||
};
|
||||
3797665A2C79FA6900C10DBD /* MPVKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */;
|
||||
productName = MPVKit;
|
||||
};
|
||||
3797665C2C79FA7500C10DBD /* MPVKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */;
|
||||
productName = MPVKit;
|
||||
};
|
||||
3797665E2C79FA7D00C10DBD /* MPVKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */;
|
||||
productName = MPVKit;
|
||||
};
|
||||
3797757C268922D100DD52A8 /* Siesta */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */;
|
||||
@@ -5328,6 +5313,21 @@
|
||||
package = 37FB285227220D8400A57617 /* XCRemoteSwiftPackageReference "SDWebImagePINPlugin" */;
|
||||
productName = SDWebImagePINPlugin;
|
||||
};
|
||||
E265D0C12C7D217000D2BB8E /* MPVKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */;
|
||||
productName = MPVKit;
|
||||
};
|
||||
E265D0C32C7D218A00D2BB8E /* MPVKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */;
|
||||
productName = MPVKit;
|
||||
};
|
||||
E265D0C52C7D21A300D2BB8E /* MPVKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */;
|
||||
productName = MPVKit;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "1f99971d9d21cffe56d0033bc5c38e9fcd5ff46ca5f7d19c76f5ba0a268ce4b6",
|
||||
"originHash" : "193e96313b1796c618e74a8b1c36659d0f16f66278ff8045f9e02c42590ae5aa",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "activelabel.swift",
|
||||
@@ -58,10 +58,10 @@
|
||||
{
|
||||
"identity" : "mpvkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/cxfksword/MPVKit",
|
||||
"location" : "https://github.com/mpvkit/MPVKit.git",
|
||||
"state" : {
|
||||
"revision" : "f646e4b625e9c8a2ff22a7e0bb5557306300be5d",
|
||||
"version" : "0.38.0"
|
||||
"revision" : "ee72059235566df8b455bff15e3f83a1c9053e78",
|
||||
"version" : "0.38.0-fix"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Cocoa
|
||||
import MPVKit
|
||||
import Libmpv
|
||||
import OpenGL.GL
|
||||
import OpenGL.GL3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user