mirror of
https://github.com/yattee/yattee.git
synced 2025-12-13 11:38:15 +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
|
## Build 192
|
||||||
* Improved thumbnail handling by @stonerl in https://github.com/yattee/yattee/pull/740
|
* Fix mpv crashing on macOS by @stonerl in https://github.com/yattee/yattee/pull/754
|
||||||
* iOS: make timestamps in comments touchable by @stonerl in https://github.com/yattee/yattee/pull/741
|
* Refreshed icons for iOS and macOS by @stonerl in https://github.com/yattee/yattee/pull/752
|
||||||
* Improvements to opening channels from Videos by @stonerl in https://github.com/yattee/yattee/pull/742
|
* Add new MPVKit repo by @stonerl in https://github.com/yattee/yattee/pull/753
|
||||||
* Allow hiding comments by @stonerl in https://github.com/yattee/yattee/pull/744
|
* Add Chinese (Simplified) - zh-Hans to LanguageCodes by @stonerl in https://github.com/yattee/yattee/pull/757
|
||||||
* Add option to exit fullscreen on end by @stonerl in https://github.com/yattee/yattee/pull/570
|
* Color changes to VideoActions by @stonerl in https://github.com/yattee/yattee/pull/759
|
||||||
* Only updateWatch status while video is playing by @stonerl in https://github.com/yattee/yattee/pull/745
|
* Hide VideoActions Bar when no buttons is visible by @stonerl in https://github.com/yattee/yattee/pull/760
|
||||||
* 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
|
|
||||||
|
|
||||||
## Previous builds
|
## Previous builds
|
||||||
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
|
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
|
||||||
@@ -27,6 +23,18 @@
|
|||||||
* Add import export of missing settings
|
* Add import export of missing settings
|
||||||
* macOS: Fix settings windows layout
|
* macOS: Fix settings windows layout
|
||||||
* Fix seek OSD layout on tvOS, revert OSD position
|
* 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: 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
|
* 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
|
* 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 {
|
final class MenuModel: ObservableObject {
|
||||||
static let shared = MenuModel()
|
static let shared = MenuModel()
|
||||||
private var cancellables = [AnyCancellable]()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
registerChildModel(AccountsModel.shared)
|
registerChildModel(AccountsModel.shared)
|
||||||
@@ -12,10 +12,16 @@ final class MenuModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func registerChildModel<T: ObservableObject>(_ model: T?) {
|
func registerChildModel<T: ObservableObject>(_ model: T?) {
|
||||||
guard !model.isNil else {
|
guard let model else {
|
||||||
return
|
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 CoreMedia
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Libmpv
|
||||||
import Logging
|
import Logging
|
||||||
import MediaPlayer
|
import MediaPlayer
|
||||||
import MPVKit
|
|
||||||
import Repeat
|
import Repeat
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -360,8 +360,8 @@ final class MPVBackend: PlayerBackend {
|
|||||||
setRate(model.currentRate)
|
setRate(model.currentRate)
|
||||||
|
|
||||||
// After the video has ended, hitting play restarts the video from the beginning.
|
// After the video has ended, hitting play restarts the video from the beginning.
|
||||||
if currentTime?.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime() &&
|
if let currentTime, currentTime.seconds.formattedAsPlaybackTime() == model.playerTime.duration.seconds.formattedAsPlaybackTime() &&
|
||||||
currentTime!.seconds > 0 && model.playerTime.duration.seconds > 0
|
currentTime.seconds > 0 && model.playerTime.duration.seconds > 0
|
||||||
{
|
{
|
||||||
seek(to: 0, seekType: .loopRestart)
|
seek(to: 0, seekType: .loopRestart)
|
||||||
}
|
}
|
||||||
@@ -464,6 +464,8 @@ final class MPVBackend: PlayerBackend {
|
|||||||
timeObserverThrottle.execute {
|
timeObserverThrottle.execute {
|
||||||
self.model.updateWatch(time: self.currentTime)
|
self.model.updateWatch(time: self.currentTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.model.updateTime(self.currentTime!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopClientUpdates() {
|
private func stopClientUpdates() {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import CoreMedia
|
import CoreMedia
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Libmpv
|
||||||
import Logging
|
import Logging
|
||||||
import MPVKit
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
import Siesta
|
import Siesta
|
||||||
import UIKit
|
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-analyzeduration", "1"))
|
||||||
checkError(mpv_set_option_string(mpv, "demuxer-lavf-probe-info", Defaults[.mpvDemuxerLavfProbeInfo]))
|
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))
|
checkError(mpv_initialize(mpv))
|
||||||
|
|
||||||
let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)
|
let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import CoreMedia
|
import CoreMedia
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import Logging
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
import UIKit
|
import UIKit
|
||||||
#endif
|
#endif
|
||||||
@@ -75,6 +76,10 @@ protocol PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PlayerBackend {
|
extension PlayerBackend {
|
||||||
|
var logger: Logger {
|
||||||
|
return Logger(label: "stream.yattee.player.backend")
|
||||||
|
}
|
||||||
|
|
||||||
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
func seek(to time: CMTime, seekType: SeekType, completionHandler: ((Bool) -> Void)? = nil) {
|
||||||
model.seek.registerSeek(at: time, type: seekType, restore: currentTime)
|
model.seek.registerSeek(at: time, type: seekType, restore: currentTime)
|
||||||
seek(to: time, seekType: seekType, completionHandler: completionHandler)
|
seek(to: time, seekType: seekType, completionHandler: completionHandler)
|
||||||
@@ -140,55 +145,87 @@ extension PlayerBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting, formatOrder: [QualityProfile.Format]) -> Stream? {
|
func bestPlayable(_ streams: [Stream], maxResolution: ResolutionSetting, formatOrder: [QualityProfile.Format]) -> Stream? {
|
||||||
// filter out non-HLS streams and streams with resolution more than maxResolution
|
logger.info("Starting bestPlayable function")
|
||||||
let nonHLSStreams = streams.filter {
|
logger.info("Total streams received: \(streams.count)")
|
||||||
$0.kind != .hls && $0.resolution <= maxResolution.value
|
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 bestResolutionStream = nonHLSStreams.max { $0.resolution < $1.resolution }
|
||||||
let bestBitrateStream = nonHLSStreams.max { $0.bitrate ?? 0 < $1.bitrate ?? 0 }
|
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 bestResolution = bestResolutionStream?.resolution ?? maxResolution.value
|
||||||
let bestBitrate = bestBitrateStream?.bitrate ?? bestResolutionStream?.resolution.bitrate ?? maxResolution.value.bitrate
|
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 {
|
if stream.kind == .hls {
|
||||||
|
logger.info("Adjusting HLS stream ID: \(stream.id)")
|
||||||
stream.resolution = bestResolution
|
stream.resolution = bestResolution
|
||||||
stream.bitrate = bestBitrate
|
stream.bitrate = bestBitrate
|
||||||
stream.format = .hls
|
stream.format = .hls
|
||||||
} else if stream.kind == .stream {
|
} else if stream.kind == .stream {
|
||||||
|
logger.info("Adjusting non-HLS stream ID: \(stream.id)")
|
||||||
stream.format = .stream
|
stream.format = .stream
|
||||||
}
|
}
|
||||||
return 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 {
|
if lhs.resolution == rhs.resolution {
|
||||||
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
|
guard let lhsFormat = QualityProfile.Format(rawValue: lhs.format.rawValue),
|
||||||
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
|
let rhsFormat = QualityProfile.Format(rawValue: rhs.format.rawValue)
|
||||||
else {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let lhsFormatIndex = formatOrder.firstIndex(of: lhsFormat) ?? Int.max
|
let lhsFormatIndex = formatOrder.firstIndex(of: lhsFormat) ?? Int.max
|
||||||
let rhsFormatIndex = formatOrder.firstIndex(of: rhsFormat) ?? 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
|
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
|
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) {
|
func updateControls(completionHandler: (() -> Void)? = nil) {
|
||||||
print("updating controls")
|
logger.info("updating controls")
|
||||||
|
|
||||||
guard model.presentingPlayer, !model.controls.presentingOverlays else {
|
guard model.presentingPlayer, !model.controls.presentingOverlays else {
|
||||||
print("ignored controls update")
|
logger.info("ignored controls update")
|
||||||
completionHandler?()
|
completionHandler?()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -196,7 +233,7 @@ extension PlayerBackend {
|
|||||||
DispatchQueue.main.async(qos: .userInteractive) {
|
DispatchQueue.main.async(qos: .userInteractive) {
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
guard UIApplication.shared.applicationState != .background else {
|
guard UIApplication.shared.applicationState != .background else {
|
||||||
print("not performing controls updates in background")
|
logger.info("not performing controls updates in background")
|
||||||
completionHandler?()
|
completionHandler?()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ extension PlayerModel {
|
|||||||
var streamByQualityProfile: Stream? {
|
var streamByQualityProfile: Stream? {
|
||||||
let profile = qualityProfile ?? .defaultProfile
|
let profile = qualityProfile ?? .defaultProfile
|
||||||
|
|
||||||
|
// First attempt: Filter by both `canPlay` and `isPreferred`
|
||||||
if let streamPreferredForProfile = backend.bestPlayable(
|
if let streamPreferredForProfile = backend.bestPlayable(
|
||||||
availableStreams.filter { backend.canPlay($0) && profile.isPreferred($0) },
|
availableStreams.filter { backend.canPlay($0) && profile.isPreferred($0) },
|
||||||
maxResolution: profile.resolution, formatOrder: profile.formats
|
maxResolution: profile.resolution, formatOrder: profile.formats
|
||||||
@@ -134,7 +135,24 @@ extension PlayerModel {
|
|||||||
return streamPreferredForProfile
|
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() {
|
func advanceToNextItem() {
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ final class PlaylistsModel: ObservableObject {
|
|||||||
.onSuccess { resource in
|
.onSuccess { resource in
|
||||||
self.error = nil
|
self.error = nil
|
||||||
if let playlists: [Playlist] = resource.typedContent() {
|
if let playlists: [Playlist] = resource.typedContent() {
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
self.playlists = playlists
|
self.playlists = playlists
|
||||||
|
}
|
||||||
PlaylistsCacheModel.shared.storePlaylist(account: account, playlists: playlists)
|
PlaylistsCacheModel.shared.storePlaylist(account: account, playlists: playlists)
|
||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,26 +5,153 @@ import Foundation
|
|||||||
// swiftlint:disable:next final_class
|
// swiftlint:disable:next final_class
|
||||||
class Stream: Equatable, Hashable, Identifiable {
|
class Stream: Equatable, Hashable, Identifiable {
|
||||||
enum Resolution: String, CaseIterable, Comparable, Defaults.Serializable {
|
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 hd2160p60
|
||||||
case hd2160p50
|
case hd2160p50
|
||||||
case hd2160p48
|
case hd2160p48
|
||||||
case hd2160p30
|
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 hd1440p60
|
||||||
case hd1440p50
|
case hd1440p50
|
||||||
case hd1440p48
|
case hd1440p48
|
||||||
case hd1440p30
|
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 hd1080p60
|
||||||
case hd1080p50
|
case hd1080p50
|
||||||
case hd1080p48
|
case hd1080p48
|
||||||
case hd1080p30
|
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 hd720p60
|
||||||
case hd720p50
|
case hd720p50
|
||||||
case hd720p48
|
case hd720p48
|
||||||
case hd720p30
|
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 sd480p30
|
||||||
|
case sd480p25
|
||||||
|
|
||||||
|
case sd428p30
|
||||||
|
case sd428p25
|
||||||
case sd360p30
|
case sd360p30
|
||||||
|
case sd360p25
|
||||||
|
case sd320p30
|
||||||
|
case sd320p25
|
||||||
case sd240p30
|
case sd240p30
|
||||||
|
case sd240p25
|
||||||
|
case sd214p30
|
||||||
|
case sd214p25
|
||||||
case sd144p30
|
case sd144p30
|
||||||
|
case sd144p25
|
||||||
|
case sd128p30
|
||||||
|
case sd128p25
|
||||||
|
|
||||||
case unknown
|
case unknown
|
||||||
|
|
||||||
var name: String {
|
var name: String {
|
||||||
@@ -59,22 +186,94 @@ class Stream: Equatable, Hashable, Identifiable {
|
|||||||
|
|
||||||
var bitrate: Int {
|
var bitrate: Int {
|
||||||
switch self {
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
return 600_000 // 0.6 Mbit/s
|
||||||
|
|
||||||
|
case .sd128p30, .sd128p25:
|
||||||
|
return 400_000 // 0.4 Mbit/s
|
||||||
|
|
||||||
case .unknown:
|
case .unknown:
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ enum LanguageCodes: String, CaseIterable {
|
|||||||
case Vietnamese = "vi"
|
case Vietnamese = "vi"
|
||||||
case Xhosa = "xh"
|
case Xhosa = "xh"
|
||||||
case Chinese = "zh"
|
case Chinese = "zh"
|
||||||
|
case Chinese_Hans = "zh-Hans"
|
||||||
case Zulu = "zu"
|
case Zulu = "zu"
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
@@ -147,6 +148,8 @@ enum LanguageCodes: String, CaseIterable {
|
|||||||
return "Xhosa"
|
return "Xhosa"
|
||||||
case .Chinese:
|
case .Chinese:
|
||||||
return "Chinese"
|
return "Chinese"
|
||||||
|
case .Chinese_Hans:
|
||||||
|
return "Chinese (Simplified)"
|
||||||
case .Zulu:
|
case .Zulu:
|
||||||
return "Zulu"
|
return "Zulu"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import GLKit
|
import GLKit
|
||||||
|
import Libmpv
|
||||||
import Logging
|
import Logging
|
||||||
import MPVKit
|
|
||||||
import OpenGLES
|
import OpenGLES
|
||||||
|
|
||||||
final class MPVOGLView: GLKView {
|
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) {
|
ScrollView(.horizontal) {
|
||||||
ScrollViewReader { scrollViewProxy in
|
ScrollViewReader { scrollViewProxy in
|
||||||
LazyHStack(spacing: 20) {
|
LazyHStack(spacing: 20) {
|
||||||
chapterViews(for: chapters[...], scrollViewProxy: scrollViewProxy)
|
chapterViews(for: chapters[...])
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 15)
|
.padding(.horizontal, 15)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if let currentChapterIndex = player.currentChapterIndex {
|
scrollToCurrentChapter(scrollViewProxy)
|
||||||
scrollViewProxy.scrollTo(currentChapterIndex, anchor: .center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.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
|
#else
|
||||||
Section { chapterViews(for: chapters[...]) }.padding(.horizontal)
|
Section { chapterViews(for: chapters[...]) }
|
||||||
|
.padding(.horizontal)
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@@ -80,7 +77,7 @@ struct ChaptersView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if !os(tvOS)
|
#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
|
ForEach(Array(chaptersToShow.indices), id: \.self) { index in
|
||||||
let chapter = chaptersToShow[index]
|
let chapter = chaptersToShow[index]
|
||||||
ChapterView(chapter: chapter, chapterIndex: index, showThumbnail: showThumbnails)
|
ChapterView(chapter: chapter, chapterIndex: index, showThumbnail: showThumbnails)
|
||||||
@@ -89,6 +86,14 @@ struct ChaptersView: View {
|
|||||||
.allowsHitTesting(clickable)
|
.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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ struct VideoActions: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAnyActionVisible() -> Bool {
|
||||||
|
return Action.allCases.contains { isVisible($0) }
|
||||||
|
}
|
||||||
|
|
||||||
func isActionable(_ action: Action) -> Bool {
|
func isActionable(_ action: Action) -> Bool {
|
||||||
switch action {
|
switch action {
|
||||||
case .share:
|
case .share:
|
||||||
@@ -198,10 +202,10 @@ struct VideoActions: View {
|
|||||||
VStack(spacing: 3) {
|
VStack(spacing: 3) {
|
||||||
Image(systemName: systemImage)
|
Image(systemName: systemImage)
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.foregroundColor(active ? Color("AppRedColor") : .accentColor)
|
.foregroundColor(active ? Color("AppRedColor") : .primary)
|
||||||
if playerActionsButtonLabelStyle.text {
|
if playerActionsButtonLabelStyle.text {
|
||||||
Text(name.localized())
|
Text(name.localized())
|
||||||
.foregroundColor(active ? Color("AppRedColor") : .secondary)
|
.foregroundColor(active ? Color("AppRedColor") : .primary)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.allowsTightening(true)
|
.allowsTightening(true)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ struct VideoDetails: View {
|
|||||||
)
|
)
|
||||||
#endif
|
#endif
|
||||||
// swiftlint:enable trailing_closure
|
// swiftlint:enable trailing_closure
|
||||||
|
if VideoActions().isAnyActionVisible() {
|
||||||
VideoActions(video: player.videoForDisplay)
|
VideoActions(video: player.videoForDisplay)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.frame(maxHeight: 50)
|
.frame(maxHeight: 50)
|
||||||
@@ -244,6 +244,13 @@ struct VideoDetails: View {
|
|||||||
.borderBottom(height: 0.5, color: Color("ControlsBorderColor"))
|
.borderBottom(height: 0.5, color: Color("ControlsBorderColor"))
|
||||||
.animation(nil, value: player.currentItem)
|
.animation(nil, value: player.currentItem)
|
||||||
.frame(minWidth: 0, maxWidth: .infinity)
|
.frame(minWidth: 0, maxWidth: .infinity)
|
||||||
|
} else {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.clear)
|
||||||
|
.frame(height: 0.5)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(Color("ControlsBorderColor"))
|
||||||
|
}
|
||||||
|
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
pageView
|
pageView
|
||||||
|
|||||||
@@ -361,9 +361,9 @@ struct PlayerSettings: View {
|
|||||||
|
|
||||||
private var captionsFontScaleSizePicker: some View {
|
private var captionsFontScaleSizePicker: some View {
|
||||||
Picker("Size", selection: $captionsFontScaleSize) {
|
Picker("Size", selection: $captionsFontScaleSize) {
|
||||||
Text("Small").tag(String("0.5"))
|
Text("Small").tag(String("0.725"))
|
||||||
Text("Medium").tag(String("1.0"))
|
Text("Medium").tag(String("1.0"))
|
||||||
Text("Large").tag(String("2.0"))
|
Text("Large").tag(String("1.5"))
|
||||||
}
|
}
|
||||||
.onChange(of: captionsFontScaleSize) { _ in
|
.onChange(of: captionsFontScaleSize) { _ in
|
||||||
PlayerModel.shared.mpvBackend.client.setSubFontSize(scaleSize: captionsFontScaleSize)
|
PlayerModel.shared.mpvBackend.client.setSubFontSize(scaleSize: captionsFontScaleSize)
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ struct YatteeApp: App {
|
|||||||
|
|
||||||
NavigationModel.shared.tabSelection = section ?? .search
|
NavigationModel.shared.tabSelection = section ?? .search
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.main.async {
|
||||||
playlists.load()
|
playlists.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -619,9 +619,6 @@
|
|||||||
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104828D3D10600D5F53C /* SDWebImageSwiftUI */; };
|
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104828D3D10600D5F53C /* SDWebImageSwiftUI */; };
|
||||||
3797104B28D3D18800D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104A28D3D18800D5F53C /* SDWebImageSwiftUI */; };
|
3797104B28D3D18800D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104A28D3D18800D5F53C /* SDWebImageSwiftUI */; };
|
||||||
3797104D28D3D19100D5F53C /* SDWebImageSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = 3797104C28D3D19100D5F53C /* 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 */; };
|
3797757D268922D100DD52A8 /* Siesta in Frameworks */ = {isa = PBXBuildFile; productRef = 3797757C268922D100DD52A8 /* Siesta */; };
|
||||||
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; };
|
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37977582268922F600DD52A8 /* InvidiousAPI.swift */; };
|
||||||
37977584268922F600DD52A8 /* 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 */; };
|
E258F38A2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
|
||||||
E258F38B2BF61BD2005B8C28 /* 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 */; };
|
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 */; };
|
E27568B92BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
|
||||||
E27568BA2BFAAC2000BDF0AF /* 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 */; };
|
E27568BB2BFAAC2000BDF0AF /* LanguageCodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E27568B82BFAAC2000BDF0AF /* LanguageCodes.swift */; };
|
||||||
@@ -1599,7 +1599,7 @@
|
|||||||
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */,
|
3797104928D3D10600D5F53C /* SDWebImageSwiftUI in Frameworks */,
|
||||||
37BD07B92698AB2E003EBB87 /* Siesta in Frameworks */,
|
37BD07B92698AB2E003EBB87 /* Siesta in Frameworks */,
|
||||||
37FB284D2722099E00A57617 /* SDWebImageWebPCoder in Frameworks */,
|
37FB284D2722099E00A57617 /* SDWebImageWebPCoder in Frameworks */,
|
||||||
3797665B2C79FA6900C10DBD /* MPVKit in Frameworks */,
|
E265D0C22C7D217000D2BB8E /* MPVKit in Frameworks */,
|
||||||
37CF8B8428535E4F00B71E37 /* SDWebImage in Frameworks */,
|
37CF8B8428535E4F00B71E37 /* SDWebImage in Frameworks */,
|
||||||
37C7367A2AC33010007630E1 /* SwiftUIIntrospect in Frameworks */,
|
37C7367A2AC33010007630E1 /* SwiftUIIntrospect in Frameworks */,
|
||||||
);
|
);
|
||||||
@@ -1624,7 +1624,7 @@
|
|||||||
375B8AB728B583BD00397B31 /* KeychainAccess in Frameworks */,
|
375B8AB728B583BD00397B31 /* KeychainAccess in Frameworks */,
|
||||||
3703205E27D2BB12007A0CB8 /* SDWebImageWebPCoder in Frameworks */,
|
3703205E27D2BB12007A0CB8 /* SDWebImageWebPCoder in Frameworks */,
|
||||||
37CF8B8628535E5A00B71E37 /* SDWebImage in Frameworks */,
|
37CF8B8628535E5A00B71E37 /* SDWebImage in Frameworks */,
|
||||||
3797665D2C79FA7500C10DBD /* MPVKit in Frameworks */,
|
E265D0C42C7D218A00D2BB8E /* MPVKit in Frameworks */,
|
||||||
3703205C27D2BAF3007A0CB8 /* SwiftyJSON in Frameworks */,
|
3703205C27D2BAF3007A0CB8 /* SwiftyJSON in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -1670,7 +1670,7 @@
|
|||||||
372915E42687E33E00F5A35B /* Defaults in Frameworks */,
|
372915E42687E33E00F5A35B /* Defaults in Frameworks */,
|
||||||
3772003B27E8EEC800CB2475 /* libbz2.tbd in Frameworks */,
|
3772003B27E8EEC800CB2475 /* libbz2.tbd in Frameworks */,
|
||||||
37BADCA9269A570B009BE4FB /* Alamofire in Frameworks */,
|
37BADCA9269A570B009BE4FB /* Alamofire in Frameworks */,
|
||||||
3797665F2C79FA7D00C10DBD /* MPVKit in Frameworks */,
|
E265D0C62C7D21A300D2BB8E /* MPVKit in Frameworks */,
|
||||||
37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */,
|
37D4B19D2671817900C925CA /* SwiftyJSON in Frameworks */,
|
||||||
3797757D268922D100DD52A8 /* Siesta in Frameworks */,
|
3797757D268922D100DD52A8 /* Siesta in Frameworks */,
|
||||||
);
|
);
|
||||||
@@ -2585,7 +2585,7 @@
|
|||||||
371AC0AB294D1A490085989E /* CachedAsyncImage */,
|
371AC0AB294D1A490085989E /* CachedAsyncImage */,
|
||||||
379325D429A265A300181CF1 /* Logging */,
|
379325D429A265A300181CF1 /* Logging */,
|
||||||
37C736792AC33010007630E1 /* SwiftUIIntrospect */,
|
37C736792AC33010007630E1 /* SwiftUIIntrospect */,
|
||||||
3797665A2C79FA6900C10DBD /* MPVKit */,
|
E265D0C12C7D217000D2BB8E /* MPVKit */,
|
||||||
);
|
);
|
||||||
productName = "Yattee (iOS)";
|
productName = "Yattee (iOS)";
|
||||||
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
|
productReference = 37D4B0C92671614900C925CA /* Yattee.app */;
|
||||||
@@ -2624,7 +2624,7 @@
|
|||||||
371AC0B1294D1C230085989E /* CachedAsyncImage */,
|
371AC0B1294D1C230085989E /* CachedAsyncImage */,
|
||||||
379325D629A265AE00181CF1 /* Logging */,
|
379325D629A265AE00181CF1 /* Logging */,
|
||||||
37C736772AC32B28007630E1 /* SwiftUIIntrospect */,
|
37C736772AC32B28007630E1 /* SwiftUIIntrospect */,
|
||||||
3797665C2C79FA7500C10DBD /* MPVKit */,
|
E265D0C32C7D218A00D2BB8E /* MPVKit */,
|
||||||
);
|
);
|
||||||
productName = "Yattee (macOS)";
|
productName = "Yattee (macOS)";
|
||||||
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
|
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
|
||||||
@@ -2702,7 +2702,7 @@
|
|||||||
377F9F75294403880043F856 /* Cache */,
|
377F9F75294403880043F856 /* Cache */,
|
||||||
371AC0B3294D1C290085989E /* CachedAsyncImage */,
|
371AC0B3294D1C290085989E /* CachedAsyncImage */,
|
||||||
379325D829A265B500181CF1 /* Logging */,
|
379325D829A265B500181CF1 /* Logging */,
|
||||||
3797665E2C79FA7D00C10DBD /* MPVKit */,
|
E265D0C52C7D21A300D2BB8E /* MPVKit */,
|
||||||
);
|
);
|
||||||
productName = Yattee;
|
productName = Yattee;
|
||||||
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
|
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
|
||||||
@@ -2822,7 +2822,7 @@
|
|||||||
374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */,
|
374D11E52943C56300CB4350 /* XCRemoteSwiftPackageReference "Cache" */,
|
||||||
371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */,
|
371AC0AA294D1A490085989E /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */,
|
||||||
379325D329A265A300181CF1 /* XCRemoteSwiftPackageReference "swift-log" */,
|
379325D329A265A300181CF1 /* XCRemoteSwiftPackageReference "swift-log" */,
|
||||||
379766592C79FA6900C10DBD /* XCRemoteSwiftPackageReference "MPVKit" */,
|
E265D0C02C7D217000D2BB8E /* XCRemoteSwiftPackageReference "MPVKit" */,
|
||||||
);
|
);
|
||||||
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
|
productRefGroup = 37D4B0CA2671614900C925CA /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -4103,7 +4103,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
|
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
|
||||||
@@ -4134,7 +4134,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||||
@@ -4165,7 +4165,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||||
@@ -4185,7 +4185,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||||
@@ -4348,7 +4348,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
@@ -4400,7 +4400,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
|
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
|
||||||
@@ -4452,7 +4452,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -4491,7 +4491,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
@@ -4525,7 +4525,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4548,7 +4548,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4573,7 +4573,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4597,7 +4597,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4623,7 +4623,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -4663,7 +4663,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -4703,7 +4703,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -4726,7 +4726,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 188;
|
CURRENT_PROJECT_VERSION = 192;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -4960,14 +4960,6 @@
|
|||||||
minimumVersion = 2.1.0;
|
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" */ = {
|
3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/bustoutsolutions/siesta";
|
repositoryURL = "https://github.com/bustoutsolutions/siesta";
|
||||||
@@ -5040,6 +5032,14 @@
|
|||||||
minimumVersion = 0.3.0;
|
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 */
|
/* End XCRemoteSwiftPackageReference section */
|
||||||
|
|
||||||
/* Begin XCSwiftPackageProductDependency section */
|
/* Begin XCSwiftPackageProductDependency section */
|
||||||
@@ -5228,21 +5228,6 @@
|
|||||||
package = 3797104728D3D10600D5F53C /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */;
|
package = 3797104728D3D10600D5F53C /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */;
|
||||||
productName = 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 */ = {
|
3797757C268922D100DD52A8 /* Siesta */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
package = 3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */;
|
package = 3797757B268922D100DD52A8 /* XCRemoteSwiftPackageReference "siesta" */;
|
||||||
@@ -5328,6 +5313,21 @@
|
|||||||
package = 37FB285227220D8400A57617 /* XCRemoteSwiftPackageReference "SDWebImagePINPlugin" */;
|
package = 37FB285227220D8400A57617 /* XCRemoteSwiftPackageReference "SDWebImagePINPlugin" */;
|
||||||
productName = 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 */
|
/* End XCSwiftPackageProductDependency section */
|
||||||
|
|
||||||
/* Begin XCVersionGroup section */
|
/* Begin XCVersionGroup section */
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "1f99971d9d21cffe56d0033bc5c38e9fcd5ff46ca5f7d19c76f5ba0a268ce4b6",
|
"originHash" : "193e96313b1796c618e74a8b1c36659d0f16f66278ff8045f9e02c42590ae5aa",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "activelabel.swift",
|
"identity" : "activelabel.swift",
|
||||||
@@ -58,10 +58,10 @@
|
|||||||
{
|
{
|
||||||
"identity" : "mpvkit",
|
"identity" : "mpvkit",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/cxfksword/MPVKit",
|
"location" : "https://github.com/mpvkit/MPVKit.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "f646e4b625e9c8a2ff22a7e0bb5557306300be5d",
|
"revision" : "ee72059235566df8b455bff15e3f83a1c9053e78",
|
||||||
"version" : "0.38.0"
|
"version" : "0.38.0-fix"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
import MPVKit
|
import Libmpv
|
||||||
import OpenGL.GL
|
import OpenGL.GL
|
||||||
import OpenGL.GL3
|
import OpenGL.GL3
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user