mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-21 18:48:19 +00:00
Compare commits
1 Commits
refactor-c
...
pr-descrip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
620a3224fc |
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@@ -10,10 +10,13 @@
|
|||||||
## 🔖 Summary
|
## 🔖 Summary
|
||||||
<!--
|
<!--
|
||||||
A concise description of the changes introduced by this PR.
|
A concise description of the changes introduced by this PR.
|
||||||
|
CodeRabbit will auto-generate a summary here if enabled.
|
||||||
Example:
|
Example:
|
||||||
“Add real-time currency conversion widget to dashboard.”
|
"Add real-time currency conversion widget to dashboard."
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
@coderabbitai summary
|
||||||
|
|
||||||
## 🏷️ Ticket / Issue
|
## 🏷️ Ticket / Issue
|
||||||
<!--
|
<!--
|
||||||
Link to the related ticket, issue or user story.
|
Link to the related ticket, issue or user story.
|
||||||
|
|||||||
@@ -449,7 +449,7 @@ export default function page() {
|
|||||||
async (data: { nativeEvent: MpvOnProgressEventPayload }) => {
|
async (data: { nativeEvent: MpvOnProgressEventPayload }) => {
|
||||||
if (isSeeking.get() || isPlaybackStopped) return;
|
if (isSeeking.get() || isPlaybackStopped) return;
|
||||||
|
|
||||||
const { position, cacheSeconds } = data.nativeEvent;
|
const { position } = data.nativeEvent;
|
||||||
// MPV reports position in seconds, convert to ms
|
// MPV reports position in seconds, convert to ms
|
||||||
const currentTime = position * 1000;
|
const currentTime = position * 1000;
|
||||||
|
|
||||||
@@ -459,12 +459,6 @@ export default function page() {
|
|||||||
|
|
||||||
progress.set(currentTime);
|
progress.set(currentTime);
|
||||||
|
|
||||||
// Update cache progress (current position + buffered seconds ahead)
|
|
||||||
if (cacheSeconds !== undefined && cacheSeconds > 0) {
|
|
||||||
const cacheEnd = currentTime + cacheSeconds * 1000;
|
|
||||||
cacheProgress.set(cacheEnd);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update URL immediately after seeking, or every 30 seconds during normal playback
|
// Update URL immediately after seeking, or every 30 seconds during normal playback
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const shouldUpdateUrl = wasJustSeeking.get();
|
const shouldUpdateUrl = wasJustSeeking.get();
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Feather } from "@expo/vector-icons";
|
import { Feather } from "@expo/vector-icons";
|
||||||
import type { PlaybackProgressInfo } from "@jellyfin/sdk/lib/generated-client/models";
|
import { useCallback, useEffect } from "react";
|
||||||
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api";
|
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
|
||||||
import { Platform } from "react-native";
|
import { Platform } from "react-native";
|
||||||
import { Pressable } from "react-native-gesture-handler";
|
import { Pressable } from "react-native-gesture-handler";
|
||||||
import GoogleCast, {
|
import GoogleCast, {
|
||||||
@@ -13,7 +10,6 @@ import GoogleCast, {
|
|||||||
useMediaStatus,
|
useMediaStatus,
|
||||||
useRemoteMediaClient,
|
useRemoteMediaClient,
|
||||||
} from "react-native-google-cast";
|
} from "react-native-google-cast";
|
||||||
import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
|
|
||||||
import { RoundButton } from "./RoundButton";
|
import { RoundButton } from "./RoundButton";
|
||||||
|
|
||||||
export function Chromecast({
|
export function Chromecast({
|
||||||
@@ -28,10 +24,6 @@ export function Chromecast({
|
|||||||
const sessionManager = GoogleCast.getSessionManager();
|
const sessionManager = GoogleCast.getSessionManager();
|
||||||
const discoveryManager = GoogleCast.getDiscoveryManager();
|
const discoveryManager = GoogleCast.getDiscoveryManager();
|
||||||
const mediaStatus = useMediaStatus();
|
const mediaStatus = useMediaStatus();
|
||||||
const api = useAtomValue(apiAtom);
|
|
||||||
const user = useAtomValue(userAtom);
|
|
||||||
|
|
||||||
const lastReportedProgressRef = useRef(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -44,53 +36,6 @@ export function Chromecast({
|
|||||||
})();
|
})();
|
||||||
}, [client, devices, castDevice, sessionManager, discoveryManager]);
|
}, [client, devices, castDevice, sessionManager, discoveryManager]);
|
||||||
|
|
||||||
// Report video progress to Jellyfin server
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
!api ||
|
|
||||||
!user?.Id ||
|
|
||||||
!mediaStatus ||
|
|
||||||
!mediaStatus.mediaInfo?.contentId
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const streamPosition = mediaStatus.streamPosition || 0;
|
|
||||||
|
|
||||||
// Report every 10 seconds
|
|
||||||
if (Math.abs(streamPosition - lastReportedProgressRef.current) < 10) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentId = mediaStatus.mediaInfo.contentId;
|
|
||||||
const positionTicks = Math.floor(streamPosition * 10000000);
|
|
||||||
const isPaused = mediaStatus.playerState === "paused";
|
|
||||||
const streamUrl = mediaStatus.mediaInfo.contentUrl || "";
|
|
||||||
const isTranscoding = streamUrl.includes("m3u8");
|
|
||||||
|
|
||||||
const progressInfo: PlaybackProgressInfo = {
|
|
||||||
ItemId: contentId,
|
|
||||||
PositionTicks: positionTicks,
|
|
||||||
IsPaused: isPaused,
|
|
||||||
PlayMethod: isTranscoding ? "Transcode" : "DirectStream",
|
|
||||||
PlaySessionId: contentId,
|
|
||||||
};
|
|
||||||
|
|
||||||
getPlaystateApi(api)
|
|
||||||
.reportPlaybackProgress({ playbackProgressInfo: progressInfo })
|
|
||||||
.then(() => {
|
|
||||||
lastReportedProgressRef.current = streamPosition;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Failed to report Chromecast progress:", error);
|
|
||||||
});
|
|
||||||
}, [
|
|
||||||
api,
|
|
||||||
user?.Id,
|
|
||||||
mediaStatus?.streamPosition,
|
|
||||||
mediaStatus?.mediaInfo?.contentId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Android requires the cast button to be present for startDiscovery to work
|
// Android requires the cast button to be present for startDiscovery to work
|
||||||
const AndroidCastButton = useCallback(
|
const AndroidCastButton = useCallback(
|
||||||
() =>
|
() =>
|
||||||
|
|||||||
Binary file not shown.
@@ -1,13 +1,10 @@
|
|||||||
package expo.modules.mpvplayer
|
package expo.modules.mpvplayer
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.AssetManager
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MPV renderer that wraps libmpv for video playback.
|
* MPV renderer that wraps libmpv for video playback.
|
||||||
@@ -29,7 +26,7 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Delegate {
|
interface Delegate {
|
||||||
fun onPositionChanged(position: Double, duration: Double, cacheSeconds: Double)
|
fun onPositionChanged(position: Double, duration: Double)
|
||||||
fun onPauseChanged(isPaused: Boolean)
|
fun onPauseChanged(isPaused: Boolean)
|
||||||
fun onLoadingChanged(isLoading: Boolean)
|
fun onLoadingChanged(isLoading: Boolean)
|
||||||
fun onReadyToSeek()
|
fun onReadyToSeek()
|
||||||
@@ -49,7 +46,6 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
// Cached state
|
// Cached state
|
||||||
private var cachedPosition: Double = 0.0
|
private var cachedPosition: Double = 0.0
|
||||||
private var cachedDuration: Double = 0.0
|
private var cachedDuration: Double = 0.0
|
||||||
private var cachedCacheSeconds: Double = 0.0
|
|
||||||
private var _isPaused: Boolean = true
|
private var _isPaused: Boolean = true
|
||||||
private var _isLoading: Boolean = false
|
private var _isLoading: Boolean = false
|
||||||
private var _playbackSpeed: Double = 1.0
|
private var _playbackSpeed: Double = 1.0
|
||||||
@@ -105,52 +101,6 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
MPVLib.create(context)
|
MPVLib.create(context)
|
||||||
MPVLib.addObserver(this)
|
MPVLib.addObserver(this)
|
||||||
|
|
||||||
/**
|
|
||||||
* Create mpv config directory and copy font files to ensure SubRip subtitles load properly on Android.
|
|
||||||
*
|
|
||||||
* Technical Background:
|
|
||||||
* ====================
|
|
||||||
* On Android, mpv requires access to a font file to render text-based subtitles, particularly SubRip (.srt)
|
|
||||||
* format subtitles. Without an available font in the config directory, mpv will fail to display subtitles
|
|
||||||
* even when subtitle tracks are properly detected and loaded.
|
|
||||||
*
|
|
||||||
* Why This Is Necessary:
|
|
||||||
* =====================
|
|
||||||
* 1. Android's font system is isolated from native libraries like mpv. While Android has system fonts,
|
|
||||||
* mpv cannot access them directly due to sandboxing and library isolation.
|
|
||||||
*
|
|
||||||
* 2. SubRip subtitles require a font to render text overlay on video. When no font is available in the
|
|
||||||
* configured directory, mpv either:
|
|
||||||
* - Fails silently (subtitles don't appear)
|
|
||||||
* - Falls back to a default font that may not support the required character set
|
|
||||||
* - Crashes or produces rendering errors
|
|
||||||
*
|
|
||||||
* 3. By placing a font file (font.ttf) in mpv's config directory and setting that directory via
|
|
||||||
* MPVLib.setOptionString("config-dir", ...), we ensure mpv has a known, accessible font source.
|
|
||||||
*
|
|
||||||
* Reference:
|
|
||||||
* =========
|
|
||||||
* This workaround is documented in the mpv-android project:
|
|
||||||
* https://github.com/mpv-android/mpv-android/issues/96
|
|
||||||
*
|
|
||||||
* The issue discusses that without a font in the config directory, SubRip subtitles fail to load
|
|
||||||
* properly on Android, and the solution is to copy a font file to a known location that mpv can access.
|
|
||||||
*/
|
|
||||||
// Create mpv config directory and copy font files
|
|
||||||
val mpvDir = File(context.getExternalFilesDir(null) ?: context.filesDir, "mpv")
|
|
||||||
//Log.i(TAG, "mpv config dir: $mpvDir")
|
|
||||||
if (!mpvDir.exists()) mpvDir.mkdirs()
|
|
||||||
// This needs to be named `subfont.ttf` else it won't work
|
|
||||||
arrayOf("subfont.ttf").forEach { fileName ->
|
|
||||||
val file = File(mpvDir, fileName)
|
|
||||||
if (file.exists()) return@forEach
|
|
||||||
context.assets
|
|
||||||
.open(fileName, AssetManager.ACCESS_STREAMING)
|
|
||||||
.copyTo(FileOutputStream(file))
|
|
||||||
}
|
|
||||||
MPVLib.setOptionString("config", "yes")
|
|
||||||
MPVLib.setOptionString("config-dir", mpvDir.path)
|
|
||||||
|
|
||||||
// Configure mpv options before initialization (based on Findroid)
|
// Configure mpv options before initialization (based on Findroid)
|
||||||
MPVLib.setOptionString("vo", "gpu")
|
MPVLib.setOptionString("vo", "gpu")
|
||||||
MPVLib.setOptionString("gpu-context", "android")
|
MPVLib.setOptionString("gpu-context", "android")
|
||||||
@@ -174,7 +124,7 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
MPVLib.setOptionString("hr-seek-framedrop", "yes")
|
MPVLib.setOptionString("hr-seek-framedrop", "yes")
|
||||||
|
|
||||||
// Subtitle settings
|
// Subtitle settings
|
||||||
MPVLib.setOptionString("sub-scale-with-window", "no")
|
MPVLib.setOptionString("sub-scale-with-window", "yes")
|
||||||
MPVLib.setOptionString("sub-use-margins", "no")
|
MPVLib.setOptionString("sub-use-margins", "no")
|
||||||
MPVLib.setOptionString("subs-match-os-language", "yes")
|
MPVLib.setOptionString("subs-match-os-language", "yes")
|
||||||
MPVLib.setOptionString("subs-fallback", "yes")
|
MPVLib.setOptionString("subs-fallback", "yes")
|
||||||
@@ -333,7 +283,6 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
MPVLib.observeProperty("pause", MPV_FORMAT_FLAG)
|
MPVLib.observeProperty("pause", MPV_FORMAT_FLAG)
|
||||||
MPVLib.observeProperty("track-list/count", MPV_FORMAT_INT64)
|
MPVLib.observeProperty("track-list/count", MPV_FORMAT_INT64)
|
||||||
MPVLib.observeProperty("paused-for-cache", MPV_FORMAT_FLAG)
|
MPVLib.observeProperty("paused-for-cache", MPV_FORMAT_FLAG)
|
||||||
MPVLib.observeProperty("demuxer-cache-duration", MPV_FORMAT_DOUBLE)
|
|
||||||
// Video dimensions for PiP aspect ratio
|
// Video dimensions for PiP aspect ratio
|
||||||
MPVLib.observeProperty("video-params/w", MPV_FORMAT_INT64)
|
MPVLib.observeProperty("video-params/w", MPV_FORMAT_INT64)
|
||||||
MPVLib.observeProperty("video-params/h", MPV_FORMAT_INT64)
|
MPVLib.observeProperty("video-params/h", MPV_FORMAT_INT64)
|
||||||
@@ -612,7 +561,7 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
when (property) {
|
when (property) {
|
||||||
"duration" -> {
|
"duration" -> {
|
||||||
cachedDuration = value
|
cachedDuration = value
|
||||||
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration, cachedCacheSeconds) }
|
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration) }
|
||||||
}
|
}
|
||||||
"time-pos" -> {
|
"time-pos" -> {
|
||||||
cachedPosition = value
|
cachedPosition = value
|
||||||
@@ -621,12 +570,9 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
|
|||||||
val shouldUpdate = _isSeeking || (now - lastProgressUpdateTime >= 1000)
|
val shouldUpdate = _isSeeking || (now - lastProgressUpdateTime >= 1000)
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
lastProgressUpdateTime = now
|
lastProgressUpdateTime = now
|
||||||
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration, cachedCacheSeconds) }
|
mainHandler.post { delegate?.onPositionChanged(cachedPosition, cachedDuration) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"demuxer-cache-duration" -> {
|
|
||||||
cachedCacheSeconds = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ class MpvPlayerView(context: Context, appContext: AppContext) : ExpoView(context
|
|||||||
|
|
||||||
// MARK: - MPVLayerRenderer.Delegate
|
// MARK: - MPVLayerRenderer.Delegate
|
||||||
|
|
||||||
override fun onPositionChanged(position: Double, duration: Double, cacheSeconds: Double) {
|
override fun onPositionChanged(position: Double, duration: Double) {
|
||||||
cachedPosition = position
|
cachedPosition = position
|
||||||
cachedDuration = duration
|
cachedDuration = duration
|
||||||
|
|
||||||
@@ -319,8 +319,7 @@ class MpvPlayerView(context: Context, appContext: AppContext) : ExpoView(context
|
|||||||
onProgress(mapOf(
|
onProgress(mapOf(
|
||||||
"position" to position,
|
"position" to position,
|
||||||
"duration" to duration,
|
"duration" to duration,
|
||||||
"progress" to if (duration > 0) position / duration else 0.0,
|
"progress" to if (duration > 0) position / duration else 0.0
|
||||||
"cacheSeconds" to cacheSeconds
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import CoreVideo
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
protocol MPVLayerRendererDelegate: AnyObject {
|
protocol MPVLayerRendererDelegate: AnyObject {
|
||||||
func renderer(_ renderer: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double, cacheSeconds: Double)
|
func renderer(_ renderer: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double)
|
||||||
func renderer(_ renderer: MPVLayerRenderer, didChangePause isPaused: Bool)
|
func renderer(_ renderer: MPVLayerRenderer, didChangePause isPaused: Bool)
|
||||||
func renderer(_ renderer: MPVLayerRenderer, didChangeLoading isLoading: Bool)
|
func renderer(_ renderer: MPVLayerRenderer, didChangeLoading isLoading: Bool)
|
||||||
func renderer(_ renderer: MPVLayerRenderer, didBecomeReadyToSeek: Bool)
|
func renderer(_ renderer: MPVLayerRenderer, didBecomeReadyToSeek: Bool)
|
||||||
@@ -44,7 +44,6 @@ final class MPVLayerRenderer {
|
|||||||
// Thread-safe state for playback
|
// Thread-safe state for playback
|
||||||
private var _cachedDuration: Double = 0
|
private var _cachedDuration: Double = 0
|
||||||
private var _cachedPosition: Double = 0
|
private var _cachedPosition: Double = 0
|
||||||
private var _cachedCacheSeconds: Double = 0
|
|
||||||
private var _isPaused: Bool = true
|
private var _isPaused: Bool = true
|
||||||
private var _playbackSpeed: Double = 1.0
|
private var _playbackSpeed: Double = 1.0
|
||||||
private var _isLoading: Bool = false
|
private var _isLoading: Bool = false
|
||||||
@@ -76,10 +75,6 @@ final class MPVLayerRenderer {
|
|||||||
get { stateQueue.sync { _cachedPosition } }
|
get { stateQueue.sync { _cachedPosition } }
|
||||||
set { stateQueue.async(flags: .barrier) { self._cachedPosition = newValue } }
|
set { stateQueue.async(flags: .barrier) { self._cachedPosition = newValue } }
|
||||||
}
|
}
|
||||||
private var cachedCacheSeconds: Double {
|
|
||||||
get { stateQueue.sync { _cachedCacheSeconds } }
|
|
||||||
set { stateQueue.async(flags: .barrier) { self._cachedCacheSeconds = newValue } }
|
|
||||||
}
|
|
||||||
private var isPaused: Bool {
|
private var isPaused: Bool {
|
||||||
get { stateQueue.sync { _isPaused } }
|
get { stateQueue.sync { _isPaused } }
|
||||||
set { stateQueue.async(flags: .barrier) { self._isPaused = newValue } }
|
set { stateQueue.async(flags: .barrier) { self._isPaused = newValue } }
|
||||||
@@ -169,7 +164,6 @@ final class MPVLayerRenderer {
|
|||||||
|
|
||||||
// Enable composite OSD mode - renders subtitles directly onto video frames using GPU
|
// Enable composite OSD mode - renders subtitles directly onto video frames using GPU
|
||||||
// This is better for PiP as subtitles are baked into the video
|
// This is better for PiP as subtitles are baked into the video
|
||||||
// NOTE: Must be set BEFORE the #if targetEnvironment check or tvOS will freeze on player exit
|
|
||||||
checkError(mpv_set_option_string(handle, "avfoundation-composite-osd", "yes"))
|
checkError(mpv_set_option_string(handle, "avfoundation-composite-osd", "yes"))
|
||||||
|
|
||||||
// Hardware decoding with VideoToolbox
|
// Hardware decoding with VideoToolbox
|
||||||
@@ -346,8 +340,7 @@ final class MPVLayerRenderer {
|
|||||||
("time-pos", MPV_FORMAT_DOUBLE),
|
("time-pos", MPV_FORMAT_DOUBLE),
|
||||||
("pause", MPV_FORMAT_FLAG),
|
("pause", MPV_FORMAT_FLAG),
|
||||||
("track-list/count", MPV_FORMAT_INT64),
|
("track-list/count", MPV_FORMAT_INT64),
|
||||||
("paused-for-cache", MPV_FORMAT_FLAG),
|
("paused-for-cache", MPV_FORMAT_FLAG)
|
||||||
("demuxer-cache-duration", MPV_FORMAT_DOUBLE)
|
|
||||||
]
|
]
|
||||||
for (name, format) in properties {
|
for (name, format) in properties {
|
||||||
mpv_observe_property(handle, 0, name, format)
|
mpv_observe_property(handle, 0, name, format)
|
||||||
@@ -491,7 +484,7 @@ final class MPVLayerRenderer {
|
|||||||
cachedDuration = value
|
cachedDuration = value
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration, cacheSeconds: self.cachedCacheSeconds)
|
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "time-pos":
|
case "time-pos":
|
||||||
@@ -506,16 +499,10 @@ final class MPVLayerRenderer {
|
|||||||
lastProgressUpdateTime = now
|
lastProgressUpdateTime = now
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration, cacheSeconds: self.cachedCacheSeconds)
|
self.delegate?.renderer(self, didUpdatePosition: self.cachedPosition, duration: self.cachedDuration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "demuxer-cache-duration":
|
|
||||||
var value = Double(0)
|
|
||||||
let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_DOUBLE, value: &value)
|
|
||||||
if status >= 0 {
|
|
||||||
cachedCacheSeconds = value
|
|
||||||
}
|
|
||||||
case "pause":
|
case "pause":
|
||||||
var flag: Int32 = 0
|
var flag: Int32 = 0
|
||||||
let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_FLAG, value: &flag)
|
let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_FLAG, value: &flag)
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ class MpvPlayerView: ExpoView {
|
|||||||
// MARK: - MPVLayerRendererDelegate
|
// MARK: - MPVLayerRendererDelegate
|
||||||
|
|
||||||
extension MpvPlayerView: MPVLayerRendererDelegate {
|
extension MpvPlayerView: MPVLayerRendererDelegate {
|
||||||
func renderer(_: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double, cacheSeconds: Double) {
|
func renderer(_: MPVLayerRenderer, didUpdatePosition position: Double, duration: Double) {
|
||||||
cachedPosition = position
|
cachedPosition = position
|
||||||
cachedDuration = duration
|
cachedDuration = duration
|
||||||
|
|
||||||
@@ -313,7 +313,6 @@ extension MpvPlayerView: MPVLayerRendererDelegate {
|
|||||||
"position": position,
|
"position": position,
|
||||||
"duration": duration,
|
"duration": duration,
|
||||||
"progress": duration > 0 ? position / duration : 0,
|
"progress": duration > 0 ? position / duration : 0,
|
||||||
"cacheSeconds": cacheSeconds,
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ export type OnProgressEventPayload = {
|
|||||||
position: number;
|
position: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
progress: number;
|
progress: number;
|
||||||
/** Seconds of video buffered ahead of current position */
|
|
||||||
cacheSeconds: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OnErrorEventPayload = {
|
export type OnErrorEventPayload = {
|
||||||
|
|||||||
Reference in New Issue
Block a user