using LibVLCSharp.Shared; namespace DualScreenDemo.Services { public class MediaService : IDisposable { private readonly LibVLC _libVLC; private readonly MediaPlayer _mediaPlayerPrimary; private readonly MediaPlayer _mediaPlayerSecondary; private Media? _media; private Media? _mediaNoAudio; private bool _disposed; public MediaService() { Core.Initialize(); _libVLC = new LibVLC( "--aout=directsound", //"--avcodec-hw=dxva2", "--network-caching=300", "--file-caching=300", "--audio-time-stretch" ); _mediaPlayerPrimary = new MediaPlayer(_libVLC); _mediaPlayerSecondary = new MediaPlayer(_libVLC); } #region Player Setup public void SetVideoFormPrimary(nint handle, int width, int height) { _mediaPlayerPrimary.Hwnd = handle; _mediaPlayerPrimary.AspectRatio = $"{width}:{height}"; _mediaPlayerPrimary.Scale = 0; // 保持原比例 } public void SetVideoFormSecondary(nint handle, int width, int height) { _mediaPlayerSecondary.Hwnd = handle; _mediaPlayerSecondary.AspectRatio = $"{width}:{height}"; _mediaPlayerSecondary.Scale = 0; } #endregion #region Playback public MediaPlayer PrimaryPlayer => _mediaPlayerPrimary; public MediaPlayer SecondaryPlayer => _mediaPlayerSecondary; public bool IsPlaying => _mediaPlayerSecondary.IsPlaying; public bool IsAtEnd() { var duration = _mediaPlayerSecondary.Media?.Duration ?? 0; var time = _mediaPlayerSecondary.Time; return duration > 0 && Math.Abs(duration - time) < 1000; } public void LoadMedia(string filePath, int audioTrackIndex = 0) { _media?.Dispose(); // 建立一個完整有聲音的 Media 給 secondary 播放器 _media = new Media(_libVLC, filePath, FromType.FromPath); _media.AddOption(":audio-output=directsound"); _media.AddOption($":audio-track={audioTrackIndex}"); // 同時準備給 primary 播放器用的無聲音版本 Media _mediaNoAudio?.Dispose(); _mediaNoAudio = new Media(_libVLC, filePath, FromType.FromPath); _mediaNoAudio.AddOption(":no-audio"); // 關閉聲音輸出 } public async Task PlayAsync() { if (_media == null || _mediaNoAudio == null) return; _mediaPlayerPrimary.Play(_mediaNoAudio); // 播放無聲音版本 await Task.Delay(100); _mediaPlayerSecondary.Play(_media); // 播放有聲音版本 await SyncPlayersAsync(); } public async Task Play() { _mediaPlayerPrimary.Play(); // 播放無聲音版本 await Task.Delay(100); _mediaPlayerSecondary.Play(); // 播放有聲音版本 await SyncPlayersAsync(); } public void Pause() { _mediaPlayerPrimary.Pause(); _mediaPlayerSecondary.Pause(); } public void Stop() { _mediaPlayerPrimary.Stop(); _mediaPlayerSecondary.Stop(); } #endregion #region Sync private async Task SyncPlayersAsync() { while (_mediaPlayerPrimary.IsPlaying && _mediaPlayerSecondary.IsPlaying) { var t1 = _mediaPlayerPrimary.Time; var t2 = _mediaPlayerSecondary.Time; if (Math.Abs(t1 - t2) > 100) _mediaPlayerSecondary.Time = t1; await Task.Delay(200); } } #endregion #region Mute public bool Mute(bool isMuted) { _mediaPlayerSecondary.Mute = isMuted; return _mediaPlayerSecondary.Mute; } #endregion #region Volume public void SetVolume(int volume) { _mediaPlayerPrimary.Volume = volume; _mediaPlayerSecondary.Volume = volume; } public int GetVolume() => _mediaPlayerSecondary?.Volume ?? 0; #endregion #region Audio Tracks public List GetAudioTracks() { var result = new List(); var media = _mediaPlayerSecondary.Media; if (media == null) return result; if (!media.IsParsed) media.Parse(MediaParseOptions.ParseLocal); var tracks = media.Tracks; if (tracks == null) return result; foreach (var track in tracks.Where(t => t.TrackType == TrackType.Audio)) { result.Add(new TrackDescription { Id = track.Id, Name = !string.IsNullOrEmpty(track.Language) ? track.Language : $"Audio Track {track.Id}" }); } return result; } public async Task SetAudioTrackToAsync(int trackIndex) { var audioTracks = GetAudioTracks(); if (trackIndex < 0 || trackIndex >= audioTracks.Count) return; if (!_mediaPlayerSecondary.IsPlaying) { _mediaPlayerSecondary.Play(); await Task.Delay(500); } _mediaPlayerSecondary.SetAudioTrack(audioTracks[trackIndex].Id); await Task.Delay(300); } #endregion #region Dispose public void Dispose() { if (_disposed) return; _mediaPlayerPrimary?.Dispose(); _mediaPlayerSecondary?.Dispose(); _media?.Dispose(); _mediaNoAudio?.Dispose(); _libVLC?.Dispose(); _mediaNoAudio = null; _media = null; _disposed = true; } #endregion } public class TrackDescription { public int Id { get; set; } public string Name { get; set; } = string.Empty; } }