mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-16 17:33:00 +01:00
WIP
This commit is contained in:
8
app.json
8
app.json
@@ -135,12 +135,10 @@
|
|||||||
["./plugins/withTrustLocalCerts.js"],
|
["./plugins/withTrustLocalCerts.js"],
|
||||||
["./plugins/withGradleProperties.js"],
|
["./plugins/withGradleProperties.js"],
|
||||||
[
|
[
|
||||||
"./plugins/addSPMDependenciesToMainTarget",
|
"./plugins/withGitPod.js",
|
||||||
{
|
{
|
||||||
"version": "0.40.0",
|
"podName": "MPVKit-GPL",
|
||||||
"repositoryUrl": "https://github.com/Alexk2309/mpvkit-private",
|
"podspecUrl": "https://raw.githubusercontent.com/Alexk2309/MPVKit/0.40.0-av/MPVKit-GPL.podspec"
|
||||||
"repoName": "mpvkit-private",
|
|
||||||
"productName": "MPVKit"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -13,7 +13,7 @@ Pod::Spec.new do |s|
|
|||||||
s.static_framework = true
|
s.static_framework = true
|
||||||
|
|
||||||
s.dependency 'ExpoModulesCore'
|
s.dependency 'ExpoModulesCore'
|
||||||
s.dependency 'MPVKit', '~> 0.40.0'
|
s.dependency 'MPVKit-GPL'
|
||||||
|
|
||||||
# Swift/Objective-C compatibility
|
# Swift/Objective-C compatibility
|
||||||
s.pod_target_xcconfig = {
|
s.pod_target_xcconfig = {
|
||||||
|
|||||||
@@ -148,12 +148,14 @@ class MpvPlayerView: ExpoView {
|
|||||||
func play() {
|
func play() {
|
||||||
intendedPlayState = true
|
intendedPlayState = true
|
||||||
renderer?.play()
|
renderer?.play()
|
||||||
|
pipController?.setPlaybackRate(1.0)
|
||||||
pipController?.updatePlaybackState()
|
pipController?.updatePlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
func pause() {
|
func pause() {
|
||||||
intendedPlayState = false
|
intendedPlayState = false
|
||||||
renderer?.pausePlayback()
|
renderer?.pausePlayback()
|
||||||
|
pipController?.setPlaybackRate(0.0)
|
||||||
pipController?.updatePlaybackState()
|
pipController?.updatePlaybackState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,9 +285,9 @@ extension MpvPlayerView: MPVSoftwareRendererDelegate {
|
|||||||
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
// Only update PiP state when PiP is active
|
// Update PiP current time for progress bar
|
||||||
if self.pipController?.isPictureInPictureActive == true {
|
if self.pipController?.isPictureInPictureActive == true {
|
||||||
self.pipController?.updatePlaybackState()
|
self.pipController?.setCurrentTimeFromSeconds(position, duration: duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.onProgress([
|
self.onProgress([
|
||||||
@@ -301,12 +303,14 @@ extension MpvPlayerView: MPVSoftwareRendererDelegate {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
// Don't update intendedPlayState here - it's only set by user actions (play/pause)
|
// Don't update intendedPlayState here - it's only set by user actions (play/pause)
|
||||||
// This prevents PiP UI flicker during seeking
|
// This prevents PiP UI flicker during seeking
|
||||||
|
|
||||||
|
// Sync timebase rate with actual playback state
|
||||||
|
self.pipController?.setPlaybackRate(isPaused ? 0.0 : 1.0)
|
||||||
|
|
||||||
self.onPlaybackStateChange([
|
self.onPlaybackStateChange([
|
||||||
"isPaused": isPaused,
|
"isPaused": isPaused,
|
||||||
"isPlaying": !isPaused,
|
"isPlaying": !isPaused,
|
||||||
])
|
])
|
||||||
// Note: Don't call updatePlaybackState() here to avoid flicker
|
|
||||||
// PiP queries pipControllerIsPlaying when it needs the state
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,12 +347,14 @@ extension MpvPlayerView: PiPControllerDelegate {
|
|||||||
print("PiP will start")
|
print("PiP will start")
|
||||||
// Sync timebase before PiP starts for smooth transition
|
// Sync timebase before PiP starts for smooth transition
|
||||||
renderer?.syncTimebase()
|
renderer?.syncTimebase()
|
||||||
pipController?.updatePlaybackState()
|
// Set current time for PiP progress bar
|
||||||
|
pipController?.setCurrentTimeFromSeconds(cachedPosition, duration: cachedDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipController(_ controller: PiPController, didStartPictureInPicture: Bool) {
|
func pipController(_ controller: PiPController, didStartPictureInPicture: Bool) {
|
||||||
print("PiP did start: \(didStartPictureInPicture)")
|
print("PiP did start: \(didStartPictureInPicture)")
|
||||||
pipController?.updatePlaybackState()
|
// Ensure current time is synced when PiP starts
|
||||||
|
pipController?.setCurrentTimeFromSeconds(cachedPosition, duration: cachedDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipController(_ controller: PiPController, willStopPictureInPicture: Bool) {
|
func pipController(_ controller: PiPController, willStopPictureInPicture: Bool) {
|
||||||
@@ -371,12 +377,16 @@ extension MpvPlayerView: PiPControllerDelegate {
|
|||||||
|
|
||||||
func pipControllerPlay(_ controller: PiPController) {
|
func pipControllerPlay(_ controller: PiPController) {
|
||||||
print("PiP play requested")
|
print("PiP play requested")
|
||||||
play()
|
intendedPlayState = true
|
||||||
|
renderer?.play()
|
||||||
|
pipController?.setPlaybackRate(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipControllerPause(_ controller: PiPController) {
|
func pipControllerPause(_ controller: PiPController) {
|
||||||
print("PiP pause requested")
|
print("PiP pause requested")
|
||||||
pause()
|
intendedPlayState = false
|
||||||
|
renderer?.pausePlayback()
|
||||||
|
pipController?.setPlaybackRate(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pipController(_ controller: PiPController, skipByInterval interval: CMTime) {
|
func pipController(_ controller: PiPController, skipByInterval interval: CMTime) {
|
||||||
@@ -394,4 +404,8 @@ extension MpvPlayerView: PiPControllerDelegate {
|
|||||||
func pipControllerDuration(_ controller: PiPController) -> Double {
|
func pipControllerDuration(_ controller: PiPController) -> Double {
|
||||||
return getDuration()
|
return getDuration()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pipControllerCurrentPosition(_ controller: PiPController) -> Double {
|
||||||
|
return getCurrentPosition()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ protocol PiPControllerDelegate: AnyObject {
|
|||||||
func pipController(_ controller: PiPController, skipByInterval interval: CMTime)
|
func pipController(_ controller: PiPController, skipByInterval interval: CMTime)
|
||||||
func pipControllerIsPlaying(_ controller: PiPController) -> Bool
|
func pipControllerIsPlaying(_ controller: PiPController) -> Bool
|
||||||
func pipControllerDuration(_ controller: PiPController) -> Double
|
func pipControllerDuration(_ controller: PiPController) -> Double
|
||||||
|
func pipControllerCurrentPosition(_ controller: PiPController) -> Double
|
||||||
}
|
}
|
||||||
|
|
||||||
final class PiPController: NSObject {
|
final class PiPController: NSObject {
|
||||||
@@ -20,6 +21,13 @@ final class PiPController: NSObject {
|
|||||||
|
|
||||||
weak var delegate: PiPControllerDelegate?
|
weak var delegate: PiPControllerDelegate?
|
||||||
|
|
||||||
|
// Timebase for PiP progress tracking
|
||||||
|
private var timebase: CMTimebase?
|
||||||
|
|
||||||
|
// Track current time for PiP progress
|
||||||
|
private var currentTime: CMTime = .zero
|
||||||
|
private var currentDuration: Double = 0
|
||||||
|
|
||||||
var isPictureInPictureSupported: Bool {
|
var isPictureInPictureSupported: Bool {
|
||||||
return AVPictureInPictureController.isPictureInPictureSupported()
|
return AVPictureInPictureController.isPictureInPictureSupported()
|
||||||
}
|
}
|
||||||
@@ -35,9 +43,29 @@ final class PiPController: NSObject {
|
|||||||
init(sampleBufferDisplayLayer: AVSampleBufferDisplayLayer) {
|
init(sampleBufferDisplayLayer: AVSampleBufferDisplayLayer) {
|
||||||
self.sampleBufferDisplayLayer = sampleBufferDisplayLayer
|
self.sampleBufferDisplayLayer = sampleBufferDisplayLayer
|
||||||
super.init()
|
super.init()
|
||||||
|
setupTimebase()
|
||||||
setupPictureInPicture()
|
setupPictureInPicture()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupTimebase() {
|
||||||
|
// Create a timebase for tracking playback time
|
||||||
|
var newTimebase: CMTimebase?
|
||||||
|
let status = CMTimebaseCreateWithSourceClock(
|
||||||
|
allocator: kCFAllocatorDefault,
|
||||||
|
sourceClock: CMClockGetHostTimeClock(),
|
||||||
|
timebaseOut: &newTimebase
|
||||||
|
)
|
||||||
|
|
||||||
|
if status == noErr, let tb = newTimebase {
|
||||||
|
timebase = tb
|
||||||
|
CMTimebaseSetTime(tb, time: .zero)
|
||||||
|
CMTimebaseSetRate(tb, rate: 0) // Start paused
|
||||||
|
|
||||||
|
// Set the control timebase on the display layer
|
||||||
|
sampleBufferDisplayLayer?.controlTimebase = tb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setupPictureInPicture() {
|
private func setupPictureInPicture() {
|
||||||
guard isPictureInPictureSupported,
|
guard isPictureInPictureSupported,
|
||||||
let displayLayer = sampleBufferDisplayLayer else {
|
let displayLayer = sampleBufferDisplayLayer else {
|
||||||
@@ -89,6 +117,34 @@ final class PiPController: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the current playback time for PiP progress display
|
||||||
|
func setCurrentTime(_ time: CMTime) {
|
||||||
|
currentTime = time
|
||||||
|
|
||||||
|
// Update the timebase to reflect current position
|
||||||
|
if let tb = timebase {
|
||||||
|
CMTimebaseSetTime(tb, time: time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always invalidate to refresh the PiP UI
|
||||||
|
updatePlaybackState()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the current playback time from seconds
|
||||||
|
func setCurrentTimeFromSeconds(_ seconds: Double, duration: Double) {
|
||||||
|
guard seconds >= 0 else { return }
|
||||||
|
currentDuration = duration
|
||||||
|
let time = CMTime(seconds: seconds, preferredTimescale: 1000)
|
||||||
|
setCurrentTime(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the playback rate on the timebase (1.0 = playing, 0.0 = paused)
|
||||||
|
func setPlaybackRate(_ rate: Float) {
|
||||||
|
if let tb = timebase {
|
||||||
|
CMTimebaseSetRate(tb, rate: Float64(rate))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - AVPictureInPictureControllerDelegate
|
// MARK: - AVPictureInPictureControllerDelegate
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
const { withXcodeProject } = require("@expo/config-plugins");
|
|
||||||
|
|
||||||
const addSPMDependenciesToMainTarget = (config, options) =>
|
|
||||||
withXcodeProject(config, (config) => {
|
|
||||||
const { version, repositoryUrl, repoName, productName } = options;
|
|
||||||
const xcodeProject = config.modResults;
|
|
||||||
|
|
||||||
// update XCRemoteSwiftPackageReference
|
|
||||||
const spmReferences =
|
|
||||||
xcodeProject.hash.project.objects.XCRemoteSwiftPackageReference;
|
|
||||||
|
|
||||||
if (!spmReferences) {
|
|
||||||
xcodeProject.hash.project.objects.XCRemoteSwiftPackageReference = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageReferenceUUID = xcodeProject.generateUuid();
|
|
||||||
|
|
||||||
xcodeProject.hash.project.objects.XCRemoteSwiftPackageReference[
|
|
||||||
`${packageReferenceUUID} /* XCRemoteSwiftPackageReference "${repoName}" */`
|
|
||||||
] = {
|
|
||||||
isa: "XCRemoteSwiftPackageReference",
|
|
||||||
repositoryURL: repositoryUrl,
|
|
||||||
requirement: {
|
|
||||||
kind: "upToNextMajorVersion",
|
|
||||||
minimumVersion: version,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// update XCSwiftPackageProductDependency
|
|
||||||
const spmProducts =
|
|
||||||
xcodeProject.hash.project.objects.XCSwiftPackageProductDependency;
|
|
||||||
|
|
||||||
if (!spmProducts) {
|
|
||||||
xcodeProject.hash.project.objects.XCSwiftPackageProductDependency = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageUUID = xcodeProject.generateUuid();
|
|
||||||
|
|
||||||
xcodeProject.hash.project.objects.XCSwiftPackageProductDependency[
|
|
||||||
`${packageUUID} /* ${productName} */`
|
|
||||||
] = {
|
|
||||||
isa: "XCSwiftPackageProductDependency",
|
|
||||||
// from step before
|
|
||||||
package: `${packageReferenceUUID} /* XCRemoteSwiftPackageReference "${repoName}" */`,
|
|
||||||
productName: productName,
|
|
||||||
};
|
|
||||||
|
|
||||||
// update PBXProject
|
|
||||||
const projectId = Object.keys(
|
|
||||||
xcodeProject.hash.project.objects.PBXProject,
|
|
||||||
).at(0);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!xcodeProject.hash.project.objects.PBXProject[projectId].packageReferences
|
|
||||||
) {
|
|
||||||
xcodeProject.hash.project.objects.PBXProject[
|
|
||||||
projectId
|
|
||||||
].packageReferences = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
xcodeProject.hash.project.objects.PBXProject[projectId].packageReferences =
|
|
||||||
[
|
|
||||||
...xcodeProject.hash.project.objects.PBXProject[projectId]
|
|
||||||
.packageReferences,
|
|
||||||
`${packageReferenceUUID} /* XCRemoteSwiftPackageReference "${repoName}" */`,
|
|
||||||
];
|
|
||||||
|
|
||||||
// update PBXBuildFile
|
|
||||||
const frameworkUUID = xcodeProject.generateUuid();
|
|
||||||
|
|
||||||
xcodeProject.hash.project.objects.PBXBuildFile[`${frameworkUUID}_comment`] =
|
|
||||||
`${productName} in Frameworks`;
|
|
||||||
xcodeProject.hash.project.objects.PBXBuildFile[frameworkUUID] = {
|
|
||||||
isa: "PBXBuildFile",
|
|
||||||
productRef: packageUUID,
|
|
||||||
productRef_comment: productName,
|
|
||||||
};
|
|
||||||
|
|
||||||
// update PBXFrameworksBuildPhase
|
|
||||||
const buildPhaseId = Object.keys(
|
|
||||||
xcodeProject.hash.project.objects.PBXFrameworksBuildPhase,
|
|
||||||
).at(0);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!xcodeProject.hash.project.objects.PBXFrameworksBuildPhase[buildPhaseId]
|
|
||||||
.files
|
|
||||||
) {
|
|
||||||
xcodeProject.hash.project.objects.PBXFrameworksBuildPhase[
|
|
||||||
buildPhaseId
|
|
||||||
].files = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
xcodeProject.hash.project.objects.PBXFrameworksBuildPhase[
|
|
||||||
buildPhaseId
|
|
||||||
].files = [
|
|
||||||
...xcodeProject.hash.project.objects.PBXFrameworksBuildPhase[buildPhaseId]
|
|
||||||
.files,
|
|
||||||
`${frameworkUUID} /* ${productName} in Frameworks */`,
|
|
||||||
];
|
|
||||||
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = addSPMDependenciesToMainTarget;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "addSPMDependenciesToMainTarget",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "app.plugin.js"
|
|
||||||
}
|
|
||||||
24
plugins/withGitPod.js
Normal file
24
plugins/withGitPod.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const { withPodfile } = require("@expo/config-plugins");
|
||||||
|
|
||||||
|
const withGitPod = (config, { podName, podspecUrl }) => {
|
||||||
|
return withPodfile(config, (config) => {
|
||||||
|
const podfile = config.modResults.contents;
|
||||||
|
|
||||||
|
const podLine = ` pod '${podName}', :podspec => '${podspecUrl}'`;
|
||||||
|
|
||||||
|
// Check if already added
|
||||||
|
if (podfile.includes(podLine)) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert after "use_expo_modules!"
|
||||||
|
config.modResults.contents = podfile.replace(
|
||||||
|
"use_expo_modules!",
|
||||||
|
`use_expo_modules!\n${podLine}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = withGitPod;
|
||||||
Reference in New Issue
Block a user