public virtual void Start() { #if UNITY_EDITOR // Subscribe for the editor playmode state changed events (pause/play) EditorApplication.playmodeStateChanged = HandleOnPlayModeChanged; #endif // Check whether MediaManager comonnent presents in the scene Debug.Assert(MediaManager.Instance != null, "[DemolitionMedia] Please add DemolitionMediaManager component to your scene. " + "You can simply attach it to an empty actor. See the documentation pdf file for more details."); // Check whether the active graphics device type is supported by the plugin var gfxDevice = SystemInfo.graphicsDeviceType; Debug.Assert(gfxDevice == UnityEngine.Rendering.GraphicsDeviceType.Direct3D9 || gfxDevice == UnityEngine.Rendering.GraphicsDeviceType.Direct3D11 || gfxDevice == UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore || gfxDevice == UnityEngine.Rendering.GraphicsDeviceType.Metal, "[DemolitionMedia] unsupported graphics device type: " + gfxDevice + ". Please use one of the supported graphics device types, which are [Direct3D9, " + "Direct3D11, OpenGLCore] on Windows platform and [Metal, OpenGLCore] on OS X platform."); // Set the global audio params anyways var audioConfiguration = AudioSettings.GetConfiguration(); //var bufferSize = AudioSettings.GetDSPBufferSize(); var bufferSize = audioConfiguration.dspBufferSize; var sampleRate = audioConfiguration.sampleRate; var channels = GetAudioChannelCount(audioConfiguration.speakerMode); NativeDll.SetAudioParams(SampleFormat.Float, sampleRate, bufferSize, channels); // Handle audio: can be enabled in run-time, so init the audio mixer anyway //if (enableAudio) initAudioMixer(); // Load the global shaders if (_shaderHapQ == null) { _shaderHapQ = Shader.Find("DemolitionMedia/HapQ"); if (_shaderHapQ == null) { Debug.LogError("[DemolitionMedia] unable to load \"DemolitionMedia/HapQ\" shader from resources. Check the plugin installation completeness."); } } if (_shaderHapQAlpha == null) { _shaderHapQAlpha = Shader.Find("DemolitionMedia/HapQAlpha"); if (_shaderHapQAlpha == null) { Debug.LogError("[DemolitionMedia] unable to load \"DemolitionMedia/HapQAlpha\" shader from resources. Check the plugin installation completeness."); } } // Load on start mechanism if (openOnStart) { Open(mediaUrl); } }
// Called by the unity audio thread to feed the samples data to the DSP buffer void OnAudioFilterRead(float[] data, int channels) { if (media != null && media.IsPlaying) { // Note: since data.Length is number of float samples for all the channels, // to get this value for a single channel, we should divide it by the channels count NativeDll.FillAudioBuffer(media.MediaId, data, 0, data.Length / channels, channels); } }
private IEnumerator UpdateNativeTexturesAtEndOfFrames() { while (Application.isPlaying) { // Wait until all frame rendering is done yield return(new WaitForEndOfFrame()); // Issue a plugin event with arbitrary integer identifier. // The plugin can distinguish between different things it needs to do based on this ID. GL.IssuePluginEvent(NativeDll.GetRenderEventFunc(), 1); } }
public void Play() { //// Timecode: reset //if (State == MediaState.Stopped) //{ // if (SyncMode == SyncMode.SyncExternalClockValue) // { // ExternalClockValue = 0.0f; // } //} _videoDecodeFramerateTime = 0.0f; NativeDll.Play(_mediaId); }
//void OnApplicationQuit() //{ //} private bool Initialize() { try { // Initialize the native plugin if (!NativeDll.Initialize()) { Debug.LogError("[DemolitionMedia] native plugin initialization failed!"); Deinitialize(); this.enabled = false; return(false); } // Get the native plugin version int major, minor, revision; NativeDll.GetPluginVersion(out major, out minor, out revision); Debug.Log("[DemolitionMedia] native plugin version: " + major + "." + minor + "." + revision); #if !UNITY_EDITOR_OSX && !UNITY_STANDALONE_OSX if (major != NativePluginVersion.MAJOR || minor != NativePluginVersion.MINOR || revision != NativePluginVersion.REVISION) { Debug.LogWarning("[DemolitionMedia] this version of C# scripts is supposed to work with native plugin version " + NativePluginVersion.MAJOR + "." + NativePluginVersion.MINOR + "." + NativePluginVersion.REVISION); Debug.LogWarning("[DemolitionMedia] you might need to update the C# scripts in order to make it work correctly with the current native plugin version"); } #endif if (NativeDll.IsDemoVersion()) { Debug.LogWarning("[DemolitionMedia] this is demo version of the DemolitionMedia plugin! Video texture will periodically have some distortions. Get the full version on the Asset Store!"); } } catch (System.DllNotFoundException e) { Debug.LogError("[DemolitionMedia] couldn't load the native plugin DLL"); throw e; } // Start the rendering coroutine /*yield return*/ StartCoroutine("UpdateNativeTexturesAtEndOfFrames"); _initialized = true; return(true); }
public bool Open(string url) { // Open the media (async) // Close the old media if one was opened previously Close(); if (_mediaId < 0) { _mediaId = NativeDll.CreateNewMediaId(); } // Prevent from crashing on null string value if (url == null) { url = ""; } var openingUrl = PreOpenImpl(url); var openingStarted = NativeDll.Open(openingUrl, enableAudio, useNativeAudioPlugin, preloadToMemory, SyncMode, _mediaId); if (!openingStarted) { _opening = false; Debug.LogError("[DemolitionMedia] Failed to start opening media from url: " + openingUrl); return(false); } _opening = true; // Force the opening state //_stateCached = MediaState.Opening; InvokeEvent(MediaEvent.Type.OpeningStarted); if (preloadToMemory) { InvokeEvent(MediaEvent.Type.PreloadingToMemoryStarted); } Debug.Log("[DemolitionMedia] Started opening media from url: " + openingUrl); // Update the cached media url mediaUrl = url; return(true); }
private void Deinitialize() { // Clean up any open movies Media[] medias = (Media[])FindObjectsOfType(typeof(Media)); if (medias != null) { for (int i = 0; i < medias.Length; i++) { medias[i].Close(); } } _instance = null; _initialized = false; Debug.Log("[DemolitionMedia] Shutting down native plugin"); NativeDll.Deinitialize(); }
private Shader GetShader() { var pixelFormat = NativeDll.GetPixelFormat(_mediaId); switch (pixelFormat) { case PixelFormat.YCoCg: return(_shaderHapQ); case PixelFormat.YCoCgAndAlphaP: return(_shaderHapQAlpha); // TODO //case PixelFormat.YUV420P: //case PixelFormat.YUV422P10LE: default: // No color conversion needed return(null); } }
private void OnOpened() { Debug.Log("[DemolitionMedia] Opened media with url: " + mediaUrl); // Cache the media parameters int width, height; NativeDll.GetResolution(_mediaId, out width, out height); VideoWidth = width; VideoHeight = height; DurationSeconds = NativeDll.GetDuration(_mediaId); VideoNumFrames = NativeDll.GetNumFrames(_mediaId); VideoFramerate = NativeDll.GetFramerate(_mediaId); bool flipX, flipY; NativeDll.GetNeedFlipVideo(_mediaId, out flipX, out flipY); VideoNeedFlipX = flipX; VideoNeedFlipY = flipY; VideoPixelFormat = NativeDll.GetPixelFormat(_mediaId); Debug.Log("[DemolitionMedia] video pixel format: " + VideoPixelFormat); // Check hardware acceleration ability bool hasHardwareAcceleration = NativeDll.IsDecodingHardwareAccelerated(_mediaId); Debug.Log("[DemolitionMedia] Hardware acceleration: " + hasHardwareAcceleration); // Execute host application-dependent code OnOpenedImpl(); // Start playing if needed if (playOnOpen) { Play(); } }
private void PopulateErrors() { MediaError error = NativeDll.GetError(_mediaId); if (error != MediaError.NoError) { if (_opening) { ResetCachedProperties(); InvokeEvent(MediaEvent.Type.OpenFailed, error); _opening = false; Debug.LogError("[DemolitionMedia] OpenFailed: " + error.ToString()); } else { InvokeEvent(MediaEvent.Type.PlaybackErrorOccured, error); Debug.LogError("[DemolitionMedia] PlaybackErrorOccured: " + error.ToString()); } } }
public void Close() { if (_mediaId < 0) { return; } InvokeEvent(MediaEvent.Type.ClosingStarted); // FIXME: call NativePlugin.Close() first? CloseImpl(); NativeDll.Close(_mediaId); mediaUrl = ""; // Force the closed media state _stateCached = MediaState.Closed; ResetCachedProperties(); _videoDecodeFramerateInterval = 1.0f; InvokeEvent(MediaEvent.Type.Closed); }
public void ToggleMute() { NativeDll.ToggleMute(_mediaId); }
public bool CreateExternalTextures() { // Get the video frame pixel format: it should be available prior the texture creation var pixelFormat = NativeDll.GetPixelFormat(_mediaId); if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Direct3D9 && pixelFormat == PixelFormat.YCoCgAndAlphaP) { // TODO: at some point use the standard way of generating errors in the native plugin for this one InvokeEvent(MediaEvent.Type.PlaybackErrorOccured, MediaError.GraphicsDeviceError); Close(); Debug.LogError("[DemolitionMedia] Hap Q Alpha is unsupported on Direct3D9 graphics device, since it uses BC4 compressed texture"); return(false); } // Check whether the native texture(s) created already bool texturesCreated = NativeDll.AreNativeTexturesCreated(_mediaId); if (!texturesCreated) { return(false); } // FIXME: clear all the textures? if (_nativeTextures.Count != 0) { // Note: this isn't needed //_nativeTexture.UpdateExternalTexture(nativeTexture); // Apply() is slow as hell, don't use it //mMovieTexture.Apply(); } var texturesCount = NativeDll.GetNativeTexturesCount(_mediaId); for (int idx = 0; idx < texturesCount; ++idx) { int width, height; NativeDll.NativeTextureFormat nativeFormat; IntPtr nativeTexture, shaderResourceView; bool result = NativeDll.GetNativeTexturePtrByIndex(_mediaId, idx, out nativeTexture, out shaderResourceView, out width, out height, out nativeFormat); if (!result || nativeTexture == IntPtr.Zero || nativeTexture.ToInt32() == 0 || width <= 0 || height <= 0 || nativeFormat == NativeDll.NativeTextureFormat.Unknown) { // FIXME: clear all the textures? Debug.LogWarning("[DemolitionMedia] native texture is invalid"); return(false); } // DX11 render backend requires SRV, others the texture handle var texPtr = SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11 ? shaderResourceView : nativeTexture; var format = TextureFormatNativeToUnity(nativeFormat); var tex = Texture2D.CreateExternalTexture(width, height, format, false, false, texPtr); tex.wrapMode = TextureWrapMode.Clamp; tex.filterMode = FilterMode.Bilinear; // Append the new texture _nativeTextures.Add(tex); } // Perform the color conversion for the first frame if needed PerformColorConversionIfNeeded(); return(true); }
public void OnDestroy() { Close(); NativeDll.DestroyMediaId(_mediaId); }
// Fills audio buffer with data (SampleFormat.Float or SampleFormat.Float_Planar) public void FillAudioBuffer(float[] buffer, int offset, int length, int channels) { NativeDll.FillAudioBuffer(_mediaId, buffer, offset, length, channels); }
public void TogglePause() { _videoDecodeFramerateTime = 0.0f; NativeDll.TogglePause(_mediaId); }
public void StepForward() { NativeDll.StepForward(_mediaId); }
private void PopulateEvents() { MediaState state = NativeDll.GetMediaState(_mediaId); // Nothing has happeded if (state == _stateCached) { return; } var finished = NativeDll.IsFinished(_mediaId); //if (_stateCached == MediaState.Closed && state == MediaState.Opening) // return; if ((_stateCached == MediaState.Closed || _stateCached == MediaState.Opening || _stateCached == MediaState.PreloadingToMemory) && state != MediaState.Error && state != MediaState.Opening && state != MediaState.PreloadingToMemory) { //Debug.Log("Cached: " + _stateCached.ToString() + ", new: " + state.ToString()); // Note: sometimes the state can change directly from PreloadingToMemory to Opened if (_stateCached == MediaState.PreloadingToMemory) { InvokeEvent(MediaEvent.Type.PreloadingToMemoryFinished); } OnOpened(); InvokeEvent(MediaEvent.Type.Opened); if (state == MediaState.Playing) { InvokeEvent(MediaEvent.Type.PlaybackStarted); } } else if (_stateCached == MediaState.PreloadingToMemory) { InvokeEvent(MediaEvent.Type.PreloadingToMemoryFinished); } else if (_stateCached == MediaState.Stopped && state == MediaState.Playing) { InvokeEvent(MediaEvent.Type.PlaybackStarted); } else if (_stateCached == MediaState.Playing && state == MediaState.Paused) { InvokeEvent(MediaEvent.Type.PlaybackSuspended); } else if (_stateCached == MediaState.Paused && state == MediaState.Playing) { InvokeEvent(MediaEvent.Type.PlaybackResumed); } else if (_stateCached == MediaState.Playing && state == MediaState.Stopped && finished) { InvokeEvent(MediaEvent.Type.PlaybackEndReached); } if ((_stateCached == MediaState.Playing || _stateCached == MediaState.Paused) && state == MediaState.Stopped) { InvokeEvent(MediaEvent.Type.PlaybackStopped); } else if (state == MediaState.Closed) { InvokeEvent(MediaEvent.Type.Closed); } // Update the cached state _stateCached = state; }
/// Sets the audio parameters public static void SetAudioParams(SampleFormat sampleFormat, int sampleRate, int bufferLength, int channels) { NativeDll.SetAudioParams(sampleFormat, sampleRate, bufferLength, channels); }
public void StepBackward() { NativeDll.StepBackward(_mediaId); }
public void SeekToFrame(int frame) { NativeDll.SeekToFrame(_mediaId, frame); }
public void SeekToTime(float seconds) { NativeDll.SeekToTime(_mediaId, seconds); }
void OnGUI() { // Check whether the source media is set if (_videoIMGUI.sourceMedia == null) { return; } // Get the media Media media = _videoIMGUI.sourceMedia; if (media == null) { return; } // Store the initial media url if (_currentMediaUrl == null) { _currentMediaUrl = media.mediaUrl; } // Draw the video _videoIMGUI.OnGUI(); // Reset the gui matrix to identity GUI.matrix = Matrix4x4.identity; // Hide the controls when the mouse is outside the video draw area if (!_active) { return; } // Set the font size // var fontSize = 50; // GUI.skin.textField.fontSize = fontSize; // GUI.skin.textArea.fontSize = fontSize; // GUI.skin.label.fontSize = fontSize; // GUI.skin.button.fontSize = fontSize; // GUI.skin.toggle.fontSize = fontSize; // GUI.skin.box.fontSize = fontSize; // GUIStyle myStyle = new GUIStyle("box"); // myStyle.fontSize = fontSize; var buttonOptions = new GUILayoutOption[] { GUILayout.ExpandWidth(false), GUILayout.Width(100) }; var area = _videoIMGUI.GetDrawRect(); int guiRows = 6, guiHeight = guiRows * 30; GUI.skin = skin; GUI.depth = _videoIMGUI.depth - 1; string mainLabel = "Demolition Media Hap " + NativePluginVersion.GetString(); if (NativeDll.IsDemoVersion()) { mainLabel += " — Demo version"; } GUILayout.Box(mainLabel); string sysInfoLabel = "Graphics device: " + SystemInfo.graphicsDeviceType.ToString(); #if UNITY_64 || UNITY_EDITOR_64 sysInfoLabel += " @ 64-bit"; #else sysInfoLabel += " @ 32-bit"; #endif GUILayout.Box(sysInfoLabel); //GUILayout.Box("Graphics device: " + SystemInfo.graphicsDeviceVersion); GUILayout.BeginHorizontal(); GUILayout.Space(20); if (GUILayout.Button("Hap", GUILayout.ExpandWidth(false))) { _currentMediaUrl = "DemolitionMedia/SampleVideos/test_street_Hap.mov"; //media.urlType = Media.UrlType.RelativeToDataPath; media.Open(_currentMediaUrl); } if (GUILayout.Button("Hap Alpha", GUILayout.ExpandWidth(false))) { _currentMediaUrl = "DemolitionMedia/SampleVideos/Loop3_All_BW_HapAlpha.mov"; //media.urlType = Media.UrlType.RelativeToDataPath; media.Open(_currentMediaUrl); } if (GUILayout.Button("Hap Q Alpha", GUILayout.ExpandWidth(false))) { _currentMediaUrl = "DemolitionMedia/SampleVideos/flying_birds_HapQAlpha.mov"; //media.urlType = Media.UrlType.RelativeToDataPath; media.Open(_currentMediaUrl); } GUILayout.EndHorizontal(); GUILayout.BeginArea(new Rect(area.x, area.y + (area.height - guiHeight), area.width, guiHeight)); GUILayout.BeginVertical(); // Row 1 GUILayout.BeginHorizontal(); if (media.IsPlaying && GUILayout.Button("Pause", buttonOptions)) { media.Pause(); } else if (!media.IsPlaying && GUILayout.Button("Play", buttonOptions)) { media.Play(); } if (GUILayout.Button("Prev frame", GUILayout.ExpandWidth(false))) { media.StepBackward(); } if (GUILayout.Button("Next frame", GUILayout.ExpandWidth(false))) { media.StepForward(); } if (GUILayout.Button("Random frame", GUILayout.ExpandWidth(false))) { media.SeekToFrame(Random.Range(0, media.VideoNumFrames)); } // Loop bool looping = media.IsLooping; bool loopingNew = GUILayout.Toggle(looping, "Loop", GUILayout.ExpandWidth(false)); if (loopingNew != looping) { if (loopingNew) { media.Loops = -1; } else { media.Loops = 1; } } // Framedrop enabled bool framedrop = media.FramedropEnabled; bool framedropNew = GUILayout.Toggle(framedrop, "Framedrop", GUILayout.ExpandWidth(false)); if (framedropNew != framedrop) { media.FramedropEnabled = framedropNew; } // Playback rate GUILayout.Space(100); float oldPlaybackSpeed = media.PlaybackSpeed; GUILayout.TextField("Speed: " + oldPlaybackSpeed.ToString("F2"), GUILayout.ExpandWidth(false)); float newPlaybackSpeed = GUILayout.HorizontalSlider( oldPlaybackSpeed, 0.5f, 15.0f, GUILayout.Width(200)); if (Math.Abs(oldPlaybackSpeed - newPlaybackSpeed) > 0.01f) { media.PlaybackSpeed = newPlaybackSpeed; } // Timecode //GUILayout.TextField("Timecode: " + media.ExternalClockValue.ToString("F2"), // GUILayout.ExpandWidth(false)); GUILayout.EndHorizontal(); // Get the active segment var currentStartFrame = media.StartFrame; var currentEndFrame = media.EndFrame; // Row 2 GUILayout.BeginHorizontal(); GUILayout.TextField("Time: " + media.CurrentTime.ToString("F2") + " / " + media.DurationSeconds.ToString("F2"), GUILayout.Width(150)); GUILayout.TextField("Frame: " + media.VideoCurrentFrame.ToString("F0") + " / " + media.VideoNumFrames.ToString("F0"), GUILayout.Width(150)); GUILayout.TextField("Resolution: " + media.VideoWidth + "x" + media.VideoHeight, GUILayout.ExpandWidth(false)); GUILayout.TextField("Video FPS: " + media.VideoFramerate, GUILayout.ExpandWidth(false)); GUILayout.TextField("Decode FPS: " + media.VideoDecodeFramerate.ToString("F1"), GUILayout.ExpandWidth(false)); int earlyDrops, lateDrops; media.GetFramedropCount(out earlyDrops, out lateDrops); GUILayout.TextField("Drops: " + earlyDrops + "/" + lateDrops, GUILayout.ExpandWidth(false)); GUILayout.TextField("Start frame: " + currentStartFrame, GUILayout.ExpandWidth(false)); GUILayout.TextField("End frame: " + Math.Max(currentEndFrame - 1, 0), GUILayout.ExpandWidth(false)); GUILayout.EndHorizontal(); // Row 1.5 var maxFrame = media.VideoNumFrames + 1; var epsFrame = 1.0f; // 1.0f / media.VideoFramerate; //float newStartFrame = GUILayout.HorizontalSlider( // currentStartFrame, 0, maxFrame, GUILayout.ExpandWidth(true)); float newStartFrame = GUILayout.HorizontalScrollbar( currentStartFrame, 0, 0.0f, maxFrame, GUILayout.ExpandWidth(true)); if (Math.Abs(currentStartFrame - newStartFrame) > epsFrame) { media.StartFrame = (int)newStartFrame; } //float newDuration = GUILayout.HorizontalSlider( // currentDuration, 0, maxFrame, GUILayout.ExpandWidth(true)); float newEndFrame = GUILayout.HorizontalScrollbar( currentEndFrame, 0, 0.0f, maxFrame, GUILayout.ExpandWidth(true)); if (Math.Abs(currentEndFrame - newEndFrame) > epsFrame && currentEndFrame <= maxFrame) { media.EndFrame = (int)newEndFrame; } // Row 3 var epsTime = media.VideoWidth > 3600 ? 5.0f / media.VideoFramerate // For large videos skip every 5 frames when seeking : 1.0f / media.VideoFramerate; float currentTime = media.CurrentTime; float newTime = GUILayout.HorizontalSlider( currentTime, 0.0f, media.DurationSeconds, GUILayout.ExpandWidth(true)); if (Math.Abs(currentTime - newTime) > epsTime && currentEndFrame < maxFrame) { media.SeekToTime(newTime); } // Row 4 GUILayout.BeginHorizontal(); GUILayout.Label("Url: ", "box", GUILayout.Width(60)); _currentMediaUrl = GUILayout.TextField(_currentMediaUrl, 256, GUILayout.MinWidth(200)); #if UNITY_64 || UNITY_EDITOR_64 media.preloadToMemory = GUILayout.Toggle(media.preloadToMemory, "Preload to memory"); #endif media.playOnOpen = GUILayout.Toggle(media.playOnOpen, "Play on open"); media.enableAudio = GUILayout.Toggle(media.enableAudio, "Open with audio"); //media.urlType = GUILayout.SelectionGrid(media.urlType, ); // TODO if (GUILayout.Button("Open", GUILayout.Width(100))) { media.Open(_currentMediaUrl); } GUILayout.EndHorizontal(); // Row 5 (optional) //if (media.IsPlaying && media.VideoDecodeFramerate < 0.8f * media.VideoFramerate) { // GUIStyle style = new GUIStyle("textField"); // style.normal.textColor = Color.red; // //GUILayout.ExpandWidth(false) // GUILayout.TextField( // media.preloadToMemory // ? "Your PC seems to be too slow for this video! Try on a faster one or use a video with smaller resolution and/or framerate" // : "Slow disk? Try preloading to memory or wait for the next loop. A faster harddisk/SSD/RAID is another option", // style); //} if (media.enableAudio && Math.Abs(newPlaybackSpeed - 1.0) > 1e-5) { GUIStyle style = new GUIStyle("textField"); style.normal.textColor = Color.red; //GUILayout.ExpandWidth(false) GUILayout.TextField( "If you want to change the playback speed, it's recommended to disable the audio (uncheck \"Open with audio\" above and press \"Open\")", style); } GUILayout.EndVertical(); GUILayout.EndArea(); // State/events area var eventsAreaRelativePosX = 0.7f; GUILayout.BeginArea(new Rect(area.x + area.width * eventsAreaRelativePosX, area.y, area.width * (1.0f - eventsAreaRelativePosX), area.width * 0.5f)); GUILayout.BeginVertical("box"); // 1. State GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUILayout.Label("State: " + media.State.ToString()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); // 2. Events GUILayout.Label("Events: ", "box"); if (_events.Count > 0) { GUILayout.BeginVertical("box"); foreach (EventEntry elem in _events) { GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); GUI.color = new Color(1.0f, 1.0f, 1.0f, elem.Timer); GUILayout.Label(elem.EventName /*, GUILayout.ExpandWidth(false)*/); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } GUILayout.EndVertical(); } GUILayout.EndVertical(); GUILayout.EndArea(); }
/// Framedrop count public void GetFramedropCount(out int earlyDrops, out int lateDrops) { NativeDll.GetFramedropCount(_mediaId, out earlyDrops, out lateDrops); }