Skip to content

Commit 3863fdb

Browse files
committed
Another video playback improvements
- Switch to more thread-safe CanvasVirtualImageSource. - This canvas type can be used in non-UI thread and can be shared between other threads while drawing frames. - Though, the canvas still need to be disposed in the UI thread. - Fix compilation error while enabling FFmpeg as MediaSource. - Ignore throwing on FrameGrabberEvent only on release build. - Fix crash while InterpolateVolumeChange is performing volume change when _currentMediaPlayer is null. - Always ignore alpha channel. - Pre-cached drawing area of the canvas. - Favor to use Task instead of ValueTask for some methods
1 parent e242dcc commit 3863fdb

File tree

1 file changed

+78
-72
lines changed

1 file changed

+78
-72
lines changed

CollapseLauncher/Classes/Helper/Background/Loaders/MediaPlayerLoader.cs

Lines changed: 78 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@
2222
using System.Threading;
2323
using System.Threading.Tasks;
2424
using System.Threading.Tasks.Dataflow;
25+
using Windows.Foundation;
2526
using Windows.Media.Playback;
2627
using Windows.Storage;
2728
using Windows.Storage.FileProperties;
2829
using Windows.UI;
2930
using ImageUI = Microsoft.UI.Xaml.Controls.Image;
3031
using static Hi3Helper.Logger;
3132
// ReSharper disable PartialTypeWithSinglePart
33+
// ReSharper disable StringLiteralTypo
3234

3335
#nullable enable
3436
namespace CollapseLauncher.Helper.Background.Loaders
@@ -39,32 +41,36 @@ namespace CollapseLauncher.Helper.Background.Loaders
3941
internal sealed partial class MediaPlayerLoader : IBackgroundMediaLoader
4042
{
4143
private readonly Color _currentDefaultColor = Color.FromArgb(0, 0, 0, 0);
42-
private bool _isCanvasCurrentlyDrawing;
44+
private int _isCanvasCurrentlyDrawing;
4345

4446
private FrameworkElement ParentUI { get; }
4547
private Compositor CurrentCompositor { get; }
4648
private DispatcherQueue CurrentDispatcherQueue { get; }
47-
private static bool IsUseVideoBgDynamicColorUpdate { get => LauncherConfig.IsUseVideoBGDynamicColorUpdate && LauncherConfig.EnableAcrylicEffect; }
49+
private static bool IsUseVideoBgDynamicColorUpdate
50+
{
51+
get => LauncherConfig.IsUseVideoBGDynamicColorUpdate && LauncherConfig.EnableAcrylicEffect;
52+
}
4853

4954
private Grid AcrylicMask { get; }
5055
private Grid OverlayTitleBar { get; }
5156
public bool IsBackgroundDimm { get; set; }
5257

5358
private FileStream? _currentMediaStream;
5459
private MediaPlayer? _currentMediaPlayer;
55-
#if USEFFMPEGFORVIDEOBG
60+
#if USEFFMPEGFORVIDEOBG
5661
private FFmpegMediaSource? _currentFFmpegMediaSource;
5762
#endif
5863

59-
private CanvasImageSource? _currentCanvasImageSource;
60-
private CanvasBitmap? _currentCanvasBitmap;
61-
private CanvasDevice? _currentCanvasDevice;
62-
private readonly int _currentCanvasWidth;
63-
private readonly int _currentCanvasHeight;
64-
private readonly float _currentCanvasDpi;
65-
private readonly MediaPlayerElement? _currentMediaPlayerFrame;
66-
private readonly Grid _currentMediaPlayerFrameParentGrid;
67-
private readonly ImageUI _currentImage;
64+
private CanvasVirtualImageSource? _currentCanvasVirtualImageSource;
65+
private CanvasBitmap? _currentCanvasBitmap;
66+
private CanvasDevice? _currentCanvasDevice;
67+
private readonly int _currentCanvasWidth;
68+
private readonly int _currentCanvasHeight;
69+
private readonly float _currentCanvasDpi;
70+
private readonly Rect _currentCanvasDrawArea;
71+
private readonly MediaPlayerElement? _currentMediaPlayerFrame;
72+
private readonly Grid _currentMediaPlayerFrameParentGrid;
73+
private readonly ImageUI _currentImage;
6874

6975
internal MediaPlayerLoader(
7076
FrameworkElement parentUI,
@@ -81,9 +87,11 @@ internal MediaPlayerLoader(
8187
_currentMediaPlayerFrameParentGrid = mediaPlayerParentGrid;
8288
_currentMediaPlayerFrame = mediaPlayerCurrent;
8389

84-
_currentCanvasWidth = (int)_currentMediaPlayerFrameParentGrid.ActualWidth;
85-
_currentCanvasHeight = (int)_currentMediaPlayerFrameParentGrid.ActualHeight;
86-
_currentCanvasDpi = 96f;
90+
_currentCanvasWidth = (int)_currentMediaPlayerFrameParentGrid.ActualWidth;
91+
_currentCanvasHeight = (int)_currentMediaPlayerFrameParentGrid.ActualHeight;
92+
_currentCanvasDpi = 96f;
93+
94+
_currentCanvasDrawArea = new Rect(0, 0, _currentCanvasWidth, _currentCanvasHeight);
8795

8896
_currentImage = mediaPlayerParentGrid.AddElementToGridRowColumn(new ImageUI
8997
{
@@ -125,12 +133,13 @@ public async Task LoadAsync(string filePath, bool isImageLoadF
125133
if (IsUseVideoBgDynamicColorUpdate)
126134
{
127135
_currentCanvasDevice ??= CanvasDevice.GetSharedDevice();
128-
_currentCanvasImageSource ??= new CanvasImageSource(_currentCanvasDevice,
129-
_currentCanvasWidth,
130-
_currentCanvasHeight,
131-
_currentCanvasDpi,
132-
CanvasAlphaMode.Premultiplied);
133-
_currentImage.Source = _currentCanvasImageSource;
136+
_currentCanvasVirtualImageSource ??= new CanvasVirtualImageSource(_currentCanvasDevice,
137+
_currentCanvasWidth,
138+
_currentCanvasHeight,
139+
_currentCanvasDpi,
140+
CanvasAlphaMode.Ignore);
141+
142+
_currentImage.Source = _currentCanvasVirtualImageSource.Source;
134143

135144
byte[] temporaryBuffer = ArrayPool<byte>.Shared.Rent(_currentCanvasWidth * _currentCanvasHeight * 4);
136145
try
@@ -141,7 +150,7 @@ public async Task LoadAsync(string filePath, bool isImageLoadF
141150
_currentCanvasHeight,
142151
Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized,
143152
_currentCanvasDpi,
144-
CanvasAlphaMode.Premultiplied);
153+
CanvasAlphaMode.Ignore);
145154
}
146155
finally
147156
{
@@ -162,9 +171,8 @@ public async Task LoadAsync(string filePath, bool isImageLoadF
162171

163172
#if !USEFFMPEGFORVIDEOBG
164173
EnsureIfFormatIsDashOrUnsupported(_currentMediaStream);
165-
#endif
166-
167174
_currentMediaPlayer ??= new MediaPlayer();
175+
#endif
168176

169177
if (WindowUtility.IsCurrentWindowInFocus())
170178
{
@@ -181,26 +189,26 @@ public async Task LoadAsync(string filePath, bool isImageLoadF
181189
#if !USEFFMPEGFORVIDEOBG
182190
_currentMediaPlayer.SetStreamSource(_currentMediaStream.AsRandomAccessStream());
183191
#else
184-
185-
_currentFFmpegMediaSource ??= await FFmpegMediaSource.CreateFromStreamAsync(CurrentMediaStream.AsRandomAccessStream());
186-
187-
await _currentFFmpegMediaSource.OpenWithMediaPlayerAsync(CurrentMediaPlayer);
188-
const string MediaInfoStrFormat = @"Playing background video with FFmpeg!
189-
Media Duration: {0}
190-
Video Resolution: {9}x{10} px
191-
Video Codec: {1}
192-
Video Codec Decoding Method: {3}
193-
Video Decoder Engine: {11}
194-
Video Bitrate: {2} bps
195-
Video Bitdepth: {11} Bits
196-
Audio Codec: {4}
197-
Audio Bitrate: {5} bps
198-
Audio Channel: {6}
199-
Audio Sample: {7}Hz
200-
Audio Bitwide: {8} Bits
201-
";
202-
Logger.LogWriteLine(
203-
string.Format(MediaInfoStrFormat,
192+
_currentFFmpegMediaSource ??= await FFmpegMediaSource.CreateFromStreamAsync(_currentMediaStream.AsRandomAccessStream());
193+
194+
await _currentFFmpegMediaSource.OpenWithMediaPlayerAsync(_currentMediaPlayer);
195+
const string mediaInfoStrFormat = """
196+
Playing background video with FFmpeg!
197+
Media Duration: {0}
198+
Video Resolution: {9}x{10} px
199+
Video Codec: {1}
200+
Video Codec Decoding Method: {3}
201+
Video Decoder Engine: {11}
202+
Video Bitrate: {2} bps
203+
Video Bitdepth: {11} Bits
204+
Audio Codec: {4}
205+
Audio Bitrate: {5} bps
206+
Audio Channel: {6}
207+
Audio Sample: {7}Hz
208+
Audio Bitwide: {8} Bits
209+
""";
210+
LogWriteLine(
211+
string.Format(mediaInfoStrFormat,
204212
_currentFFmpegMediaSource.Duration.ToString("c"), // 0
205213
_currentFFmpegMediaSource.CurrentVideoStream?.CodecName ?? "No Video Stream", // 1
206214
_currentFFmpegMediaSource.CurrentVideoStream?.Bitrate ?? 0, // 2
@@ -213,8 +221,7 @@ public async Task LoadAsync(string filePath, bool isImageLoadF
213221
_currentFFmpegMediaSource.CurrentAudioStream?.BitsPerSample ?? 0, // 8
214222
_currentFFmpegMediaSource.CurrentVideoStream?.PixelWidth ?? 0, // 9
215223
_currentFFmpegMediaSource.CurrentVideoStream?.PixelHeight ?? 0, // 10
216-
_currentFFmpegMediaSource.CurrentVideoStream?.BitsPerSample ?? 0, // 11
217-
_currentFFmpegMediaSource.CurrentVideoStream?.DecoderEngine ?? 0 // 12
224+
_currentFFmpegMediaSource.CurrentVideoStream?.BitsPerSample ?? 0 // 11
218225
), LogType.Debug, true);
219226
#endif
220227
_currentMediaPlayer.IsVideoFrameServerEnabled = IsUseVideoBgDynamicColorUpdate;
@@ -241,24 +248,26 @@ public async Task LoadAsync(string filePath, bool isImageLoadF
241248

242249
public void DisposeMediaModules()
243250
{
251+
#if !USEFFMPEGFORVIDEOBG
244252
if (_currentMediaPlayer != null)
245253
{
246254
_currentMediaPlayer.VideoFrameAvailable -= FrameGrabberEvent;
247255
_currentMediaPlayer.Dispose();
248256
Interlocked.Exchange(ref _currentMediaPlayer, null);
249257
}
258+
#endif
250259

251260
if (IsUseVideoBgDynamicColorUpdate)
252261
{
253-
while (_isCanvasCurrentlyDrawing)
262+
while (_isCanvasCurrentlyDrawing == 1)
254263
{
255264
Thread.Sleep(100);
256265
}
257266
}
258267

259-
if (_currentCanvasImageSource != null)
268+
if (_currentCanvasVirtualImageSource != null)
260269
{
261-
Interlocked.Exchange(ref _currentCanvasImageSource, null);
270+
Interlocked.Exchange(ref _currentCanvasVirtualImageSource, null);
262271
}
263272

264273
if (_currentCanvasBitmap != null)
@@ -313,37 +322,34 @@ await ColorPaletteUtility.ApplyAccentColor(ParentUI,
313322
true);
314323
}
315324

316-
private static async ValueTask<StorageFile> GetFileAsStorageFile(string filePath)
325+
private static async Task<StorageFile> GetFileAsStorageFile(string filePath)
317326
=> await StorageFile.GetFileFromPathAsync(filePath);
318327

319328
private void FrameGrabberEvent(MediaPlayer mediaPlayer, object args)
320329
{
321-
if (_isCanvasCurrentlyDrawing)
322-
{
323-
return;
324-
}
325-
326-
Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, true);
330+
CanvasDrawingSession? drawingSession = null;
327331
try
328332
{
329-
CurrentDispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, RunImpl);
333+
Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, 1);
334+
mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap);
335+
drawingSession = _currentCanvasVirtualImageSource?.CreateDrawingSession(_currentDefaultColor, _currentCanvasDrawArea);
336+
drawingSession?.DrawImage(_currentCanvasBitmap);
330337
}
331-
catch (Exception e)
338+
catch
339+
#if DEBUG
340+
(Exception e)
332341
{
333-
LogWriteLine($"[FrameGrabberEvent] Error drawing frame to canvas.\r\n{e}", LogType.Error, true);
342+
LogWriteLine($"[FrameGrabberEvent] Error while drawing frame to bitmap.\r\n{e}", LogType.Warning, true);
334343
}
335-
finally
344+
#else
336345
{
337-
Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, false);
346+
// ignored
338347
}
339-
340-
return;
341-
342-
void RunImpl()
348+
#endif
349+
finally
343350
{
344-
using CanvasDrawingSession canvasDrawingSession = _currentCanvasImageSource!.CreateDrawingSession(_currentDefaultColor);
345-
mediaPlayer.CopyFrameToVideoSurface(_currentCanvasBitmap);
346-
canvasDrawingSession.DrawImage(_currentCanvasBitmap);
351+
CurrentDispatcherQueue.TryEnqueue(() => drawingSession?.Dispose());
352+
Interlocked.Exchange(ref _isCanvasCurrentlyDrawing, 0);
347353
}
348354
}
349355

@@ -485,18 +491,18 @@ public void Unmute()
485491
LauncherConfig.SetAndSaveConfigValue("BackgroundAudioIsMute", false);
486492
}
487493

488-
private async ValueTask InterpolateVolumeChange(float from, float to, bool isMute)
494+
private async Task InterpolateVolumeChange(float from, float to, bool isMute)
489495
{
490-
if (_currentMediaPlayer == null) return;
491-
492496
double tFrom = from;
493497
double tTo = to;
494498

495499
double current = tFrom;
496500
double inc = isMute ? -0.05 : 0.05;
497501

498502
Loops:
499-
current += inc;
503+
if (_currentMediaPlayer == null) return;
504+
505+
current += inc;
500506
_currentMediaPlayer.Volume = current;
501507

502508
await Task.Delay(10);

0 commit comments

Comments
 (0)