Merge branch 'develop' into fix/external-sub-selection
Some checks failed
🏗️ Build Apps / 🤖 Build Android APK (Phone) (push) Has been cancelled
🏗️ Build Apps / 🤖 Build Android APK (TV) (push) Has been cancelled
🏗️ Build Apps / 🍎 Build iOS IPA (Phone) (push) Has been cancelled
🏗️ Build Apps / 🍎 Build iOS IPA (Phone - Unsigned) (push) Has been cancelled
🔒 Lockfile Consistency Check / 🔍 Check bun.lock and package.json consistency (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (actions) (push) Has been cancelled
🛡️ CodeQL Analysis / 🔎 Analyze with CodeQL (javascript-typescript) (push) Has been cancelled
🏷️🔀Merge Conflict Labeler / 🏷️ Labeling Merge Conflicts (push) Has been cancelled
🚦 Security & Quality Gate / 📝 Validate PR Title (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Vulnerable Dependencies (push) Has been cancelled
🚦 Security & Quality Gate / 🚑 Expo Doctor Check (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (check) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (format) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (lint) (push) Has been cancelled
🚦 Security & Quality Gate / 🔍 Lint & Test (typecheck) (push) Has been cancelled

This commit is contained in:
Fredrik Burmester
2026-01-13 22:43:05 +01:00
22 changed files with 2358 additions and 15 deletions

View File

@@ -430,6 +430,57 @@ class MPVLayerRenderer(private val context: Context) : MPVLib.EventObserver {
MPVLib.setPropertyDouble("panscan", panscanValue)
}
// MARK: - Technical Info
fun getTechnicalInfo(): Map<String, Any> {
val info = mutableMapOf<String, Any>()
// Video dimensions
MPVLib.getPropertyInt("video-params/w")?.takeIf { it > 0 }?.let {
info["videoWidth"] = it
}
MPVLib.getPropertyInt("video-params/h")?.takeIf { it > 0 }?.let {
info["videoHeight"] = it
}
// Video codec
MPVLib.getPropertyString("video-format")?.let {
info["videoCodec"] = it
}
// Audio codec
MPVLib.getPropertyString("audio-codec-name")?.let {
info["audioCodec"] = it
}
// FPS (container fps)
MPVLib.getPropertyDouble("container-fps")?.takeIf { it > 0 }?.let {
info["fps"] = it
}
// Video bitrate (bits per second)
MPVLib.getPropertyInt("video-bitrate")?.takeIf { it > 0 }?.let {
info["videoBitrate"] = it
}
// Audio bitrate (bits per second)
MPVLib.getPropertyInt("audio-bitrate")?.takeIf { it > 0 }?.let {
info["audioBitrate"] = it
}
// Demuxer cache duration (seconds of video buffered)
MPVLib.getPropertyDouble("demuxer-cache-duration")?.let {
info["cacheSeconds"] = it
}
// Dropped frames
MPVLib.getPropertyInt("frame-drop-count")?.let {
info["droppedFrames"] = it
}
return info
}
// MARK: - MPVLib.EventObserver
override fun eventProperty(property: String) {

View File

@@ -173,6 +173,11 @@ class MpvPlayerModule : Module() {
view.isZoomedToFill()
}
// Technical info function
AsyncFunction("getTechnicalInfo") { view: MpvPlayerView ->
view.getTechnicalInfo()
}
// Defines events that the view can send to JavaScript
Events("onLoad", "onPlaybackStateChange", "onProgress", "onError", "onTracksReady")
}

View File

@@ -330,6 +330,12 @@ class MpvPlayerView(context: Context, appContext: AppContext) : ExpoView(context
return _isZoomedToFill
}
// MARK: - Technical Info
fun getTechnicalInfo(): Map<String, Any> {
return renderer?.getTechnicalInfo() ?: emptyMap()
}
// MARK: - MPVLayerRenderer.Delegate
override fun onPositionChanged(position: Double, duration: Double) {

View File

@@ -763,4 +763,64 @@ final class MPVLayerRenderer {
getProperty(handle: handle, name: "aid", format: MPV_FORMAT_INT64, value: &aid)
return Int(aid)
}
// MARK: - Technical Info
func getTechnicalInfo() -> [String: Any] {
guard let handle = mpv else { return [:] }
var info: [String: Any] = [:]
// Video dimensions
var videoWidth: Int64 = 0
var videoHeight: Int64 = 0
if getProperty(handle: handle, name: "video-params/w", format: MPV_FORMAT_INT64, value: &videoWidth) >= 0 {
info["videoWidth"] = Int(videoWidth)
}
if getProperty(handle: handle, name: "video-params/h", format: MPV_FORMAT_INT64, value: &videoHeight) >= 0 {
info["videoHeight"] = Int(videoHeight)
}
// Video codec
if let videoCodec = getStringProperty(handle: handle, name: "video-format") {
info["videoCodec"] = videoCodec
}
// Audio codec
if let audioCodec = getStringProperty(handle: handle, name: "audio-codec-name") {
info["audioCodec"] = audioCodec
}
// FPS (container fps)
var fps: Double = 0
if getProperty(handle: handle, name: "container-fps", format: MPV_FORMAT_DOUBLE, value: &fps) >= 0 && fps > 0 {
info["fps"] = fps
}
// Video bitrate (bits per second)
var videoBitrate: Int64 = 0
if getProperty(handle: handle, name: "video-bitrate", format: MPV_FORMAT_INT64, value: &videoBitrate) >= 0 && videoBitrate > 0 {
info["videoBitrate"] = Int(videoBitrate)
}
// Audio bitrate (bits per second)
var audioBitrate: Int64 = 0
if getProperty(handle: handle, name: "audio-bitrate", format: MPV_FORMAT_INT64, value: &audioBitrate) >= 0 && audioBitrate > 0 {
info["audioBitrate"] = Int(audioBitrate)
}
// Demuxer cache duration (seconds of video buffered)
var cacheSeconds: Double = 0
if getProperty(handle: handle, name: "demuxer-cache-duration", format: MPV_FORMAT_DOUBLE, value: &cacheSeconds) >= 0 {
info["cacheSeconds"] = cacheSeconds
}
// Dropped frames
var droppedFrames: Int64 = 0
if getProperty(handle: handle, name: "frame-drop-count", format: MPV_FORMAT_INT64, value: &droppedFrames) >= 0 {
info["droppedFrames"] = Int(droppedFrames)
}
return info
}
}

View File

@@ -173,6 +173,11 @@ public class MpvPlayerModule: Module {
return view.isZoomedToFill()
}
// Technical info function
AsyncFunction("getTechnicalInfo") { (view: MpvPlayerView) -> [String: Any] in
return view.getTechnicalInfo()
}
// Defines events that the view can send to JavaScript
Events("onLoad", "onPlaybackStateChange", "onProgress", "onError", "onTracksReady")
}

View File

@@ -282,6 +282,12 @@ class MpvPlayerView: ExpoView {
return _isZoomedToFill
}
// MARK: - Technical Info
func getTechnicalInfo() -> [String: Any] {
return renderer?.getTechnicalInfo() ?? [:]
}
deinit {
pipController?.stopPictureInPicture()
renderer?.stop()

View File

@@ -89,6 +89,8 @@ export interface MpvPlayerViewRef {
// Video scaling
setZoomedToFill: (zoomed: boolean) => Promise<void>;
isZoomedToFill: () => Promise<boolean>;
// Technical info
getTechnicalInfo: () => Promise<TechnicalInfo>;
}
export type SubtitleTrack = {
@@ -106,3 +108,15 @@ export type AudioTrack = {
channels?: number;
selected?: boolean;
};
export type TechnicalInfo = {
videoWidth?: number;
videoHeight?: number;
videoCodec?: string;
audioCodec?: string;
fps?: number;
videoBitrate?: number;
audioBitrate?: number;
cacheSeconds?: number;
droppedFrames?: number;
};

View File

@@ -101,6 +101,10 @@ export default React.forwardRef<MpvPlayerViewRef, MpvPlayerViewProps>(
isZoomedToFill: async () => {
return await nativeRef.current?.isZoomedToFill();
},
// Technical info
getTechnicalInfo: async () => {
return await nativeRef.current?.getTechnicalInfo();
},
}));
return <NativeView ref={nativeRef} {...props} />;