MediaPlayerExtension.cs 9.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#nullable enable

using System;
using Uno.Media.Playback;
using Windows.Media.Core;
using Uno.Extensions;
using System.IO;
using Uno.Foundation.Logging;
using System.Collections.Generic;
using Uno;
using Uno.Helpers;
using System.Linq;
using System.Threading.Tasks;
using Windows.Media.Playback;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.Foundation;
using Windows.UI.Xaml.Controls;
using System.Diagnostics.CodeAnalysis;
using Windows.ApplicationModel.Background;
using Uno.Foundation.Extensibility;
using Windows.UI.Xaml.Controls.Maps;
using System.Numerics;
R
rafael-rosa-knowcode 已提交
24 25
using Uno.Logging;
using Windows.UI.Xaml;
26
using Atk;
27
using System.Runtime.CompilerServices;
28 29 30 31 32 33 34

[assembly: ApiExtension(typeof(IMediaPlayerExtension), typeof(Uno.UI.Media.MediaPlayerExtension))]

namespace Uno.UI.Media;

public partial class MediaPlayerExtension : IMediaPlayerExtension
{
35
	private static ConditionalWeakTable<Windows.Media.Playback.MediaPlayer, MediaPlayerExtension> _instances = new();
36 37 38 39

	private Uri? _uri;
	private List<Uri>? _playlistItems;
	private readonly Windows.Media.Playback.MediaPlayer _owner;
R
Rafael Rosa 已提交
40
	private GtkMediaPlayer? _player;
R
rafael-rosa-knowcode 已提交
41 42 43 44 45
	private bool _updatingPosition;
	private bool _isPlayRequested;
	private bool _isPlayerPrepared;
	private int _playlistIndex;
	private TimeSpan _naturalDuration;
46
	private bool _isLoopingEnabled;
47
	private bool _isLoopingAllEnabled;
48
	private double _playbackRate;
49 50 51 52 53 54 55 56 57 58 59 60 61 62

	public MediaPlayerExtension(object owner)
	{
		if (owner is Windows.Media.Playback.MediaPlayer player)
		{
			_owner = player;
		}
		else
		{
			throw new InvalidOperationException($"MediaPlayerPresenterExtension must be initialized with a MediaPlayer instance");
		}

		lock (_instances)
		{
63
			_instances.Add(_owner, this);
64 65 66 67 68 69 70 71 72 73 74
		}
	}

	internal static MediaPlayerExtension? GetByMediaPlayer(Windows.Media.Playback.MediaPlayer mediaPlayer)
	{
		lock (_instances)
		{
			return _instances.TryGetValue(mediaPlayer, out var instance) ? instance : null;
		}
	}

R
Rafael Rosa 已提交
75
	internal GtkMediaPlayer? GtkMediaPlayer
76 77 78 79 80 81 82 83 84 85 86
	{
		get => _player;
		set
		{
			if (value != null)
			{
				_player = value;
				InitializePlayer();
			}
		}
	}
R
Rafael Rosa 已提交
87

R
rafael-rosa-knowcode 已提交
88
	public IMediaPlayerEventsExtension? Events { get; set; }
89 90 91 92 93 94 95 96

	private void InitializePlayer()
	{
		if (_player is null)
		{
			return;
		}

R
rafael-rosa-knowcode 已提交
97 98 99 100 101 102 103 104
		_player.OnSourceFailed -= OnError;
		_player.OnSourceLoaded -= OnPrepared;
		_player.OnSourceEnded -= OnCompletion;
		_player.OnTimeUpdate -= OnTimeUpdate;
		_player.OnSourceFailed += OnError;
		_player.OnSourceLoaded += OnPrepared;
		_player.OnSourceEnded += OnCompletion;
		_player.OnTimeUpdate += OnTimeUpdate;
105
		_player.OnVideoRatioChanged += OnVideoRatioChanged;
106

R
rafael-rosa-knowcode 已提交
107 108
		_owner.PlaybackSession.PlaybackStateChanged -= OnStatusChanged;
		_owner.PlaybackSession.PlaybackStateChanged += OnStatusChanged;
109 110 111

		ApplyVideoSource();
	}
R
Rafael Rosa 已提交
112

R
rafael-rosa-knowcode 已提交
113 114 115 116 117 118 119 120 121 122 123 124 125
	public TimeSpan Position
	{
		get => TimeSpan.FromSeconds(_player?.CurrentPosition ?? 0);
		set
		{
			if (!_updatingPosition)
			{
				_updatingPosition = true;

				try
				{
					if (_owner.PlaybackSession.PlaybackState != MediaPlaybackState.None && _player is not null && _player.Source is not null)
					{
R
rafael-rosa-knowcode 已提交
126
						_player.CurrentPosition = (int)value.TotalMilliseconds;
R
rafael-rosa-knowcode 已提交
127 128 129 130 131 132 133 134 135 136
						OnSeekComplete();
					}
				}
				finally
				{
					_updatingPosition = false;
				}
			}
		}
	}
137 138 139 140 141 142 143 144 145 146 147

	private void ApplyVideoSource()
	{
		if (_player is not null && _uri is not null)
		{
			_player.Source = _uri.OriginalString;
		}
	}

	public void Pause()
	{
148 149 150 151 152 153 154 155 156 157
		if (_owner.PlaybackSession.PlaybackState == MediaPlaybackState.Playing
				&& _player != null
				&& _player.CurrentState == MediaPlayerState.Playing)
		{
			_player?.Pause();
			_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Paused;
		}
		if (_owner.PlaybackSession.PlaybackState == MediaPlaybackState.Paused
				&& _player != null
				&& _player.CurrentState != MediaPlayerState.Paused)
158 159 160 161 162 163 164 165
		{
			_player?.Pause();
			_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Paused;
		}
	}

	public void InitializeSource()
	{
R
rafael-rosa-knowcode 已提交
166 167 168 169 170
		NaturalDuration = TimeSpan.Zero;
		if (Position != TimeSpan.Zero)
		{
			Position = TimeSpan.Zero;
		}
171 172 173 174 175

		if (_owner.Source == null)
		{
			return;
		}
176 177
		_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Opening;
		InitializePlayer();
178

179
		switch (_owner.Source)
180
		{
181
			case MediaPlaybackList playlist when playlist.Items.Count > 0:
182
				SetPlaylistItems(playlist);
183 184 185 186 187 188 189 190
				if (_playlistItems is not null)
				{
					_uri = _playlistItems[0];
				}
				else
				{
					throw new InvalidOperationException("Playlist Items could not be set");
				}
191
				break;
192

193 194 195
			case MediaPlaybackItem item:
				_uri = item.Source.Uri;
				break;
196

197 198 199
			case MediaSource source:
				_uri = source.Uri;
				break;
200

201

202 203
			default:
				throw new InvalidOperationException("Unsupported media source type");
204
		}
205 206 207
		ApplyVideoSource();
		Events?.RaiseSourceChanged();

208 209 210 211 212
		// Set the player back to the paused state, so that the
		// transport controls can be shown properly.
		// This may need to be changed when the initialization of libVLC
		// can be taken into account, as well as the media status.
		_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Paused;
213
	}
R
Rafael Rosa 已提交
214

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
	public void ReInitializeSource()
	{
		NaturalDuration = TimeSpan.Zero;
		if (Position != TimeSpan.Zero)
		{
			Position = TimeSpan.Zero;
		}

		if (_owner.Source == null)
		{
			return;
		}
		_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Opening;
		InitializePlayer();
		ApplyVideoSource();
		Events?.RaiseSourceChanged();

		// Set the player back to the paused state, so that the
		// transport controls can be shown properly.
		// This may need to be changed when the initialization of libVLC
		// can be taken into account, as well as the media status.
		_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Paused;
	}

239 240 241 242 243 244
	private void SetPlaylistItems(MediaPlaybackList playlist)
	{
		_playlistItems = playlist.Items
			.Select(i => i.Source.Uri)
			.ToList();
	}
R
Rafael Rosa 已提交
245

246 247 248 249 250 251 252 253
	public void Stop()
	{
		if (_owner.PlaybackSession.PlaybackState == MediaPlaybackState.Playing)
		{
			_player?.Stop();
			_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Paused;
		}
	}
R
Rafael Rosa 已提交
254

255 256 257 258 259 260 261 262 263
	public void Play()
	{
		if (_owner.Source == null || _player == null)
		{
			return;
		}

		try
		{
R
rafael-rosa-knowcode 已提交
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
			// If we reached the end of media, we need to reset position to 0
			if (_owner.PlaybackSession.PlaybackState == MediaPlaybackState.None)
			{
				_owner.PlaybackSession.Position = TimeSpan.Zero;
			}

			_isPlayRequested = true;

			if (_isPlayerPrepared)
			{
				if (_player != null)
				{
					_player.PlaybackRate = 1;
					_player.Play();
				}
				_owner.PlaybackSession.PlaybackState = MediaPlaybackState.Playing;
			}
281 282 283 284 285 286 287
			else
			{
				if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
				{
					this.Log().Debug("The player is not prepared yet, delaying playback");
				}
			}
288
		}
R
rafael-rosa-knowcode 已提交
289
		catch (global::System.Exception ex)
290
		{
R
rafael-rosa-knowcode 已提交
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
			OnMediaFailed(ex);
		}
	}

	public void Initialize()
		=> InitializePlayer();

	public double PlaybackRate
	{
		get => _playbackRate;
		set
		{
			_playbackRate = value;
			if (_player is not null)
			{
				_player.PlaybackRate = value;
			}
		}
	}

	public bool IsLoopingEnabled
	{
		get => _isLoopingEnabled;
		set
		{
			_isLoopingEnabled = value;
317 318 319 320 321 322 323 324 325
		}
	}

	public bool IsLoopingAllEnabled
	{
		get => _isLoopingAllEnabled;
		set
		{
			_isLoopingAllEnabled = value;
326 327 328
		}
	}

329 330 331 332 333 334 335 336 337 338 339
	public MediaPlayerState CurrentState
	{
		get => _player == null ? default(MediaPlayerState) : _player.CurrentState;
		internal set
		{
			if (_player != null)
			{
				_player.CurrentState = value;
			}
		}
	}
340

R
rafael-rosa-knowcode 已提交
341 342 343 344 345 346 347 348 349 350
	public TimeSpan NaturalDuration
	{
		get => _naturalDuration;
		internal set
		{
			_naturalDuration = value;

			Events?.NaturalDurationChanged();
		}
	}
351

R
rafael-rosa-knowcode 已提交
352 353
	public bool IsProtected
			=> false;
354

R
rafael-rosa-knowcode 已提交
355 356
	public double BufferingProgress
		=> 0.0;
357

R
rafael-rosa-knowcode 已提交
358 359
	public bool CanPause
		=> true;
360

R
rafael-rosa-knowcode 已提交
361 362 363 364 365 366 367 368 369 370 371 372
	public bool CanSeek
		=> true;

	public MediaPlayerAudioDeviceType AudioDeviceType { get; set; }

	public MediaPlayerAudioCategory AudioCategory { get; set; }

	public TimeSpan TimelineControllerPositionOffset
	{
		get => Position;
		set => Position = value;
	}
373

R
rafael-rosa-knowcode 已提交
374 375 376 377
	public bool RealTimePlayback { get; set; }

	public double AudioBalance { get; set; }

378 379
	public bool? IsVideo { get; set; }

380 381 382 383 384
	public void SetTransportControlsBounds(Rect bounds)
	{
		_player?.SetTransportControlsBounds(bounds);
	}

R
rafael-rosa-knowcode 已提交
385 386
	public void SetUriSource(Uri uri)
	{
R
Rafael Rosa 已提交
387 388 389 390
		if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
		{
			this.Log().Debug($"MediaPlayerExtension.SetUriSource({uri})");
		}
R
rafael-rosa-knowcode 已提交
391 392 393 394 395
		if (_player is not null)
		{
			_player.Source = uri.OriginalString;
		}
	}
396 397

	public void SetFileSource(IStorageFile file) => throw new NotImplementedException();
R
Rafael Rosa 已提交
398

399
	public void SetStreamSource(IRandomAccessStream stream) => throw new NotImplementedException();
R
Rafael Rosa 已提交
400

401
	public void SetMediaSource(IMediaSource source) => throw new NotImplementedException();
R
Rafael Rosa 已提交
402

403
	public void StepForwardOneFrame() => throw new NotImplementedException();
R
Rafael Rosa 已提交
404

405
	public void StepBackwardOneFrame() => throw new NotImplementedException();
R
Rafael Rosa 已提交
406

407
	public void SetSurfaceSize(Size size) => throw new NotImplementedException();
R
Rafael Rosa 已提交
408

R
rafael-rosa-knowcode 已提交
409 410
	public void ToggleMute()
	{
R
rafael-rosa-knowcode 已提交
411
		if (_player is not null)
R
rafael-rosa-knowcode 已提交
412
		{
R
rafael-rosa-knowcode 已提交
413
			_player?.Mute(_owner.IsMuted);
R
rafael-rosa-knowcode 已提交
414 415 416
		}
	}

417
	public void OnOptionChanged(string name, object value) => throw new NotImplementedException();
R
rafael-rosa-knowcode 已提交
418 419 420 421 422 423 424

	public void Dispose()
	{
		_instances.Remove(_owner);

		TryDisposePlayer();
	}
R
Rafael Rosa 已提交
425

R
rafael-rosa-knowcode 已提交
426 427 428 429 430 431 432 433
	private void TryDisposePlayer()
	{
		if (_player != null)
		{
			_isPlayRequested = false;
			_isPlayerPrepared = false;
		}
	}
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455

	public void PreviousTrack()
	{
		// Play next item in playlist, if any
		if (_playlistItems != null && _playlistIndex > 0)
		{
			_uri = _playlistItems[--_playlistIndex];
			ReInitializeSource();
			Play();
		}
	}

	public void NextTrack()
	{
		// Play next item in playlist, if any
		if (_playlistItems != null && _playlistIndex < _playlistItems.Count - 1)
		{
			_uri = _playlistItems[++_playlistIndex];
			ReInitializeSource();
			Play();
		}
	}
456
}