[MediaController] Change P/Invoke function of playlist (#793)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Remoting / MediaController / MediaControlServer.cs
1 /*
2  * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.Threading.Tasks;
20 using Tizen.Applications;
21 using Native = Interop.MediaControllerServer;
22 using NativePlaylist = Interop.MediaControllerPlaylist;
23
24 namespace Tizen.Multimedia.Remoting
25 {
26     /// <summary>
27     /// Provides a means to set playback information and metadata and receive commands from clients.
28     /// </summary>
29     /// <seealso cref="MediaControllerManager"/>
30     /// <seealso cref="MediaController"/>
31     /// <since_tizen> 4 </since_tizen>
32     public static partial class MediaControlServer
33     {
34         private static IntPtr _handle;
35         private static bool? _isRunning;
36         private static string _serverName;
37
38         /// <summary>
39         /// Gets a value indicating whether the server is running.
40         /// </summary>
41         /// <value>true if the server has started; otherwise, false.</value>
42         /// <seealso cref="Start"/>
43         /// <seealso cref="Stop"/>
44         /// <since_tizen> 4 </since_tizen>
45         public static bool IsRunning
46         {
47             get
48             {
49                 if (_isRunning.HasValue == false)
50                 {
51                     _isRunning = GetRunningState();
52                 }
53
54                 return _isRunning.Value;
55             }
56         }
57
58         private static bool GetRunningState()
59         {
60             IntPtr handle = IntPtr.Zero;
61             try
62             {
63                 Native.ConnectDb(out handle).ThrowIfError("Failed to retrieve the running state.");
64
65                 Native.CheckServerExist(handle, Applications.Application.Current.ApplicationInfo.ApplicationId,
66                     out var value).ThrowIfError("Failed to retrieve the running state.");
67
68                 return value;
69             }
70             finally
71             {
72                 if (handle != IntPtr.Zero)
73                 {
74                     Native.DisconnectDb(handle);
75                 }
76             }
77         }
78
79         private static void EnsureInitializedIfRunning()
80         {
81             if (_handle != IntPtr.Zero)
82             {
83                 return;
84             }
85
86             if (IsRunning == false)
87             {
88                 throw new InvalidOperationException("The server is not running.");
89             }
90
91             Initialize();
92         }
93
94         private static IntPtr Handle
95         {
96             get
97             {
98                 EnsureInitializedIfRunning();
99
100                 return _handle;
101             }
102         }
103
104         private static void Initialize()
105         {
106             Native.Create(out _handle).ThrowIfError("Failed to create media controller server.");
107
108             try
109             {
110                 RegisterPlaybackActionCommandReceivedEvent();
111                 RegisterPlaybackPositionCommandReceivedEvent();
112                 RegisterPlaylistCommandReceivedEvent();
113                 RegisterShuffleModeCommandReceivedEvent();
114                 RegisterRepeatModeCommandReceivedEvent();
115                 RegisterCustomCommandReceivedEvent();
116                 RegisterCommandCompletedEvent();
117                 RegisterSearchCommandReceivedEvent();
118
119                 _serverName = Application.Current.ApplicationInfo.ApplicationId;
120                 _isRunning = true;
121             }
122             catch
123             {
124                 Native.Destroy(_handle);
125                 _playbackCommandCallback = null;
126                 _handle = IntPtr.Zero;
127                 _serverName = null;
128                 throw;
129             }
130         }
131
132         /// <summary>
133         /// Starts the media control server.
134         /// </summary>
135         /// <remarks>
136         /// When the server starts, <see cref="MediaControllerManager.ServerStarted"/> will be raised.
137         /// </remarks>
138         /// <privilege>http://tizen.org/privilege/mediacontroller.server</privilege>
139         /// <exception cref="InvalidOperationException">An internal error occurs.</exception>
140         /// <exception cref="UnauthorizedAccessException">Caller does not have required privilege.</exception>
141         /// <seealso cref="MediaControllerManager.ServerStarted"/>
142         /// <since_tizen> 4 </since_tizen>
143         public static void Start()
144         {
145             Initialize();
146         }
147
148         /// <summary>
149         /// Stops the media control server.
150         /// </summary>
151         /// <remarks>
152         /// When the server stops, <see cref="MediaControllerManager.ServerStopped"/> will be raised.
153         /// </remarks>
154         /// <exception cref="InvalidOperationException">
155         ///     The server is not running .<br/>
156         ///     -or-<br/>
157         ///     An internal error occurs.
158         /// </exception>
159         /// <seealso cref="MediaControllerManager.ServerStopped"/>
160         /// <since_tizen> 4 </since_tizen>
161         public static void Stop()
162         {
163             EnsureInitializedIfRunning();
164
165             Native.Destroy(_handle).ThrowIfError("Failed to stop the server.");
166
167             _handle = IntPtr.Zero;
168             _playbackCommandCallback = null;
169             _isRunning = false;
170         }
171
172         /// <summary>
173         /// Updates playback state and playback position.</summary>
174         /// <param name="state">The playback state.</param>
175         /// <param name="position">The playback position in milliseconds.</param>
176         /// <exception cref="ArgumentException"><paramref name="state"/> is not valid.</exception>
177         /// <exception cref="ArgumentOutOfRangeException"><paramref name="position"/> is less than zero.</exception>
178         /// <exception cref="InvalidOperationException">
179         ///     The server is not running .<br/>
180         ///     -or-<br/>
181         ///     An internal error occurs.
182         /// </exception>
183         /// <since_tizen> 4 </since_tizen>
184         public static void SetPlaybackState(MediaControlPlaybackState state, long position)
185         {
186             ValidationUtil.ValidateEnum(typeof(MediaControlPlaybackState), state, nameof(state));
187
188             if (position < 0)
189             {
190                 throw new ArgumentOutOfRangeException(nameof(position), position, "position can't be less than zero.");
191             }
192
193             Native.SetPlaybackState(Handle, state.ToNative()).ThrowIfError("Failed to set playback state.");
194
195             Native.SetPlaybackPosition(Handle, (ulong)position).ThrowIfError("Failed to set playback position.");
196
197             Native.UpdatePlayback(Handle).ThrowIfError("Failed to set playback.");
198         }
199
200         private static void SetMetadata(MediaControllerNativeAttribute attribute, string value)
201         {
202             if (value != null)
203             {
204                 Native.SetMetadata(Handle, attribute, value).ThrowIfError($"Failed to set metadata({attribute}).");
205             }
206         }
207
208         /// <summary>
209         /// Updates metadata information.
210         /// </summary>
211         /// <param name="metadata">The metadata to update.</param>
212         /// <exception cref="ArgumentNullException"><paramref name="metadata"/> is null.</exception>
213         /// <exception cref="InvalidOperationException">
214         ///     The server is not running .<br/>
215         ///     -or-<br/>
216         ///     An internal error occurs.
217         /// </exception>
218         /// <since_tizen> 4 </since_tizen>
219         public static void SetMetadata(MediaControlMetadata metadata)
220         {
221             if (metadata == null)
222             {
223                 throw new ArgumentNullException(nameof(metadata));
224             }
225
226             SetMetadata(MediaControllerNativeAttribute.Title, metadata.Title);
227             SetMetadata(MediaControllerNativeAttribute.Artist, metadata.Artist);
228             SetMetadata(MediaControllerNativeAttribute.Album, metadata.Album);
229             SetMetadata(MediaControllerNativeAttribute.Author, metadata.Author);
230             SetMetadata(MediaControllerNativeAttribute.Genre, metadata.Genre);
231             SetMetadata(MediaControllerNativeAttribute.Duration, metadata.Duration);
232             SetMetadata(MediaControllerNativeAttribute.Date, metadata.Date);
233             SetMetadata(MediaControllerNativeAttribute.Copyright, metadata.Copyright);
234             SetMetadata(MediaControllerNativeAttribute.Description, metadata.Description);
235             SetMetadata(MediaControllerNativeAttribute.TrackNumber, metadata.TrackNumber);
236             SetMetadata(MediaControllerNativeAttribute.Picture, metadata.AlbumArtPath);
237             SetMetadata(MediaControllerNativeAttribute.Season, metadata.EncodedSeason);
238             SetMetadata(MediaControllerNativeAttribute.Episode, metadata.EncodedEpisode);
239             SetMetadata(MediaControllerNativeAttribute.Resolution, metadata.EncodedResolution);
240
241             Native.UpdateMetadata(Handle).ThrowIfError("Failed to set metadata.");
242         }
243
244         /// <summary>
245         /// Updates the shuffle mode.
246         /// </summary>
247         /// <param name="enabled">A value indicating whether the shuffle mode is enabled.</param>
248         /// <exception cref="InvalidOperationException">
249         ///     The server is not running .<br/>
250         ///     -or-<br/>
251         ///     An internal error occurs.
252         /// </exception>
253         /// <since_tizen> 4 </since_tizen>
254         public static void SetShuffleModeEnabled(bool enabled)
255         {
256             Native.UpdateShuffleMode(Handle, enabled ? MediaControllerNativeShuffleMode.On : MediaControllerNativeShuffleMode.Off).
257                 ThrowIfError("Failed to set shuffle mode.");
258         }
259
260         /// <summary>
261         /// Updates the repeat mode.
262         /// </summary>
263         /// <param name="mode">A value indicating the repeat mode.</param>
264         /// <exception cref="InvalidOperationException">
265         ///     The server is not running .<br/>
266         ///     -or-<br/>
267         ///     An internal error occurs.
268         /// </exception>
269         /// <exception cref="ArgumentException"><paramref name="mode"/> is invalid.</exception>
270         /// <since_tizen> 4 </since_tizen>
271         public static void SetRepeatMode(MediaControlRepeatMode mode)
272         {
273             ValidationUtil.ValidateEnum(typeof(MediaControlRepeatMode), mode, nameof(mode));
274
275             Native.UpdateRepeatMode(Handle, mode.ToNative()).ThrowIfError("Failed to set repeat mode.");
276         }
277
278         /// <summary>
279         /// Sets the index of current playing media.
280         /// </summary>
281         /// <param name="index">The index of current playing media.</param>
282         /// <exception cref="ArgumentNullException"><paramref name="index"/> is null.</exception>
283         /// <exception cref="InvalidOperationException">
284         ///     The server is not running .<br/>
285         ///     -or-<br/>
286         ///     An internal error occurs.
287         /// </exception>
288         /// <since_tizen> 5 </since_tizen>
289         [Obsolete("Please do not use! This will be deprecated. Please use SetInfoOfCurrentPlayingMedia instead.")]
290         public static void SetIndexOfCurrentPlayingMedia(string index)
291         {
292             if (index == null)
293             {
294                 throw new ArgumentNullException(nameof(index));
295             }
296
297             Native.SetIndexOfCurrentPlayingMedia(Handle, index)
298                 .ThrowIfError("Failed to set the index of current playing media");
299
300             Native.UpdatePlayback(Handle).ThrowIfError("Failed to set playback.");
301         }
302
303         /// <summary>
304         /// Sets the playlist name and index of current playing media.
305         /// </summary>
306         /// <param name="playlistName">The playlist name of current playing media.</param>
307         /// <param name="index">The index of current playing media.</param>
308         /// <exception cref="ArgumentNullException">
309         /// <paramref name="playlistName"/> or <paramref name="index"/> is null.
310         /// </exception>
311         /// <exception cref="InvalidOperationException">
312         ///     The server is not running .<br/>
313         ///     -or-<br/>
314         ///     An internal error occurs.
315         /// </exception>
316         /// <since_tizen> 5 </since_tizen>
317         public static void SetInfoOfCurrentPlayingMedia(string playlistName, string index)
318         {
319             if (playlistName == null)
320             {
321                 throw new ArgumentNullException(nameof(playlistName));
322             }
323             if (index == null)
324             {
325                 throw new ArgumentNullException(nameof(index));
326             }
327
328             Native.SetInfoOfCurrentPlayingMedia(Handle, playlistName, index)
329                 .ThrowIfError("Failed to set the playlist name and index of current playing media");
330
331             Native.UpdatePlayback(Handle).ThrowIfError("Failed to set playback.");
332         }
333
334         /// <summary>
335         /// Delete playlist.
336         /// </summary>
337         /// <param name="playlist">The name of playlist.</param>
338         /// <exception cref="ArgumentNullException"><paramref name="playlist"/> is null.</exception>
339         /// <exception cref="InvalidOperationException">
340         ///     The server is not running .<br/>
341         ///     -or-<br/>
342         ///     An internal error occurs.
343         /// </exception>
344         /// <since_tizen> 5 </since_tizen>
345         public static void RemovePlaylist(MediaControlPlaylist playlist)
346         {
347             if (playlist == null)
348             {
349                 throw new ArgumentNullException(nameof(playlist));
350             }
351
352             Native.DeletePlaylist(Handle, playlist.Handle);
353             playlist.Dispose();
354         }
355
356         // Saves the playlist to the persistent storage.
357         internal static void SavePlaylist(IntPtr playlistHandle)
358         {
359             Native.SavePlaylist(Handle, playlistHandle).ThrowIfError("Failed to save playlist");
360         }
361
362         // Gets the playlist handle by name.
363         internal static IntPtr GetPlaylistHandle(string name)
364         {
365             NativePlaylist.GetPlaylistHandle(_serverName, name, out IntPtr playlistHandle).
366                 ThrowIfError("Failed to get playlist handle by name");
367
368             return playlistHandle;
369         }
370
371         /// <summary>
372         /// Gets the active clients.
373         /// </summary>
374         /// <exception cref="InvalidOperationException">
375         ///     The server is not running .<br/>
376         ///     -or-<br/>
377         ///     An internal error occurs.
378         /// </exception>
379         /// <returns>the activated client ids.</returns>
380         /// <since_tizen> 5 </since_tizen>
381         public static IEnumerable<string> GetActivatedClients()
382         {
383             var clientIds = new List<string>();
384
385             Native.ActivatedClientCallback activatedClientCallback = (name, _) =>
386             {
387                 clientIds.Add(name);
388                 return true;
389             };
390
391             Native.ForeachActivatedClient(Handle, activatedClientCallback).
392                 ThrowIfError("Failed to get activated client.");
393
394             return clientIds.AsReadOnly();
395         }
396
397         /// <summary>
398         /// Requests commands to the client.
399         /// </summary>
400         /// <remarks>
401         /// The client can request the command to execute <see cref="Command"/>, <br/>
402         /// and then, the server receive the result of each request(command).
403         /// </remarks>
404         /// <param name="command">A <see cref="Command"/> class.</param>
405         /// <param name="clientId">The client Id to send command.</param>
406         /// <returns><see cref="Bundle"/> represents the extra data from client and it can be null.</returns>
407         /// <exception cref="ArgumentNullException">
408         /// <paramref name="command"/> or <paramref name="clientId"/> is null.
409         /// </exception>
410         /// <exception cref="InvalidOperationException">
411         ///     The server has already been stopped.<br/>
412         ///     -or-<br/>
413         ///     An internal error occurs.
414         /// </exception>
415         /// <since_tizen> 5 </since_tizen>
416         public static async Task<Bundle> RequestAsync(Command command, string clientId)
417         {
418             if (command == null)
419             {
420                 throw new ArgumentNullException(nameof(command));
421             }
422             if (clientId == null)
423             {
424                 throw new ArgumentNullException(nameof(clientId));
425             }
426
427             command.SetRequestInformation(clientId);
428
429             var tcs = new TaskCompletionSource<MediaControllerError>();
430             string reqeustId = null;
431             Bundle bundle = null;
432
433             EventHandler<CommandCompletedEventArgs> eventHandler = (s, e) =>
434             {
435                 if (e.RequestId == reqeustId)
436                 {
437                     bundle = e.Bundle;
438                     tcs.TrySetResult(e.Result);
439                 }
440             };
441
442             try
443             {
444                 CommandCompleted += eventHandler;
445
446                 reqeustId = command.Request(Handle);
447
448                 (await tcs.Task).ThrowIfError("Failed to request event.");
449
450                 return bundle;
451             }
452             finally
453             {
454                 CommandCompleted -= eventHandler;
455             }
456         }
457
458         /// <summary>
459         /// Sends the result of each command.
460         /// </summary>
461         /// <param name="command">The command that return to client.</param>
462         /// <param name="result">The result of <paramref name="command"/>.</param>
463         /// <param name="bundle">The extra data.</param>
464         /// <exception cref="ArgumentNullException"><paramref name="command"/> is null.</exception>
465         /// <exception cref="InvalidOperationException">
466         ///     The server is not running .<br/>
467         ///     -or-<br/>
468         ///     An internal error occurs.
469         /// </exception>
470         /// <since_tizen> 5 </since_tizen>
471         public static void Response(Command command, int result, Bundle bundle)
472         {
473             if (command == null)
474             {
475                 throw new ArgumentNullException(nameof(command));
476             }
477
478             command.Response(Handle, result, bundle);
479         }
480
481         /// <summary>
482         /// Sends the result of each command.
483         /// </summary>
484         /// <param name="command">The command that return to client.</param>
485         /// <param name="result">The result of <paramref name="command"/>.</param>
486         /// <exception cref="ArgumentNullException"><paramref name="command"/> is null.</exception>
487         /// <exception cref="InvalidOperationException">
488         ///     The server is not running .<br/>
489         ///     -or-<br/>
490         ///     An internal error occurs.
491         /// </exception>
492         /// <since_tizen> 5 </since_tizen>
493         public static void Response(Command command, int result)
494         {
495             if (command == null)
496             {
497                 throw new ArgumentNullException(nameof(command));
498             }
499
500             command.Response(Handle, result, null);
501         }
502
503         #region Capabilities
504         /// <summary>
505         /// Sets the content type of latest played media.
506         /// </summary>
507         /// <param name="type">A value indicating the content type of the latest played media.</param>
508         /// <exception cref="InvalidOperationException">
509         ///     The server is not running .<br/>
510         ///     -or-<br/>
511         ///     An internal error occurs.
512         /// </exception>
513         /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
514         /// <since_tizen> 5 </since_tizen>
515         public static void SetPlaybackContentType(MediaControlContentType type)
516         {
517             ValidationUtil.ValidateEnum(typeof(MediaControlContentType), type, nameof(type));
518
519             Native.SetPlaybackContentType(Handle, type).ThrowIfError("Failed to set playback content type.");
520
521             Native.UpdatePlayback(Handle).ThrowIfError("Failed to set playback.");
522         }
523
524         /// <summary>
525         /// Sets the path of icon.
526         /// </summary>
527         /// <param name="path">The path of icon.</param>
528         /// <exception cref="InvalidOperationException">
529         ///     The server is not running .<br/>
530         ///     -or-<br/>
531         ///     An internal error occurs.
532         /// </exception>
533         /// <exception cref="ArgumentNullException"><paramref name="path"/> is invalid.</exception>
534         /// <since_tizen> 5 </since_tizen>
535         public static void SetIconPath(string path)
536         {
537             if (path == null)
538             {
539                 throw new ArgumentNullException(nameof(path));
540             }
541
542             Native.SetIconPath(Handle, path).ThrowIfError("Failed to set uri path.");
543         }
544
545         /// <summary>
546         /// Sets the capabilities by <see cref="MediaControlPlaybackCommand"/>.
547         /// </summary>
548         /// <param name="capabilities">The set of <see cref="MediaControlPlaybackCommand"/> and <see cref="MediaControlCapabilitySupport"/>.</param>
549         /// <exception cref="InvalidOperationException">
550         ///     The server is not running .<br/>
551         ///     -or-<br/>
552         ///     An internal error occurs.
553         /// </exception>
554         /// <exception cref="ArgumentException"><paramref name="capabilities"/> is invalid.</exception>
555         /// <since_tizen> 5 </since_tizen>
556         public static void SetPlaybackCapabilities(Dictionary<MediaControlPlaybackCommand, MediaControlCapabilitySupport> capabilities)
557         {
558             foreach (var pair in capabilities)
559             {
560                 ValidationUtil.ValidateEnum(typeof(MediaControlPlaybackCommand), pair.Key, nameof(pair.Key));
561                 ValidationUtil.ValidateEnum(typeof(MediaControlCapabilitySupport), pair.Value, nameof(pair.Value));
562
563                 SetPlaybackCapability(pair.Key, pair.Value);
564                 Native.SetPlaybackCapability(Handle, pair.Key.ToNative(), pair.Value).
565                     ThrowIfError("Failed to set playback capability.");
566             }
567
568             Native.SaveAndNotifyPlaybackCapabilityUpdated(Handle).ThrowIfError("Failed to update playback capability.");
569         }
570
571         /// <summary>
572         /// Sets the capabilities by <see cref="MediaControlPlaybackCommand"/>.
573         /// </summary>
574         /// <param name="action">A playback command.</param>
575         /// <param name="support">A value indicating whether the <paramref name="action"/> is supported or not.</param>
576         /// <exception cref="InvalidOperationException">
577         ///     The server is not running .<br/>
578         ///     -or-<br/>
579         ///     An internal error occurs.
580         /// </exception>
581         /// <exception cref="ArgumentException"><paramref name="action"/> or <paramref name="support"/> is invalid.</exception>
582         /// <since_tizen> 5 </since_tizen>
583         public static void SetPlaybackCapability(MediaControlPlaybackCommand action, MediaControlCapabilitySupport support)
584         {
585             ValidationUtil.ValidateEnum(typeof(MediaControlPlaybackCommand), action, nameof(action));
586             ValidationUtil.ValidateEnum(typeof(MediaControlCapabilitySupport), support, nameof(support));
587
588             Native.SetPlaybackCapability(Handle, action.ToNative(), support).ThrowIfError("Failed to set playback capability.");
589
590             Native.SaveAndNotifyPlaybackCapabilityUpdated(Handle).ThrowIfError("Failed to update playback capability.");
591         }
592
593         /// <summary>
594         /// Sets the <see cref="MediaControlCapabilitySupport"/> indicating shuffle mode is supported or not.
595         /// </summary>
596         /// <param name="support">A value indicating whether the shuffle mode is supported or not.</param>
597         /// <exception cref="InvalidOperationException">
598         ///     The server is not running .<br/>
599         ///     -or-<br/>
600         ///     An internal error occurs.
601         /// </exception>
602         /// <exception cref="ArgumentException"><paramref name="support"/> is invalid.</exception>
603         /// <since_tizen> 5 </since_tizen>
604         public static void SetShuffleModeCapability(MediaControlCapabilitySupport support)
605         {
606             ValidationUtil.ValidateEnum(typeof(MediaControlCapabilitySupport), support, nameof(support));
607
608             Native.SetShuffleModeCapability(Handle, support).ThrowIfError("Failed to set shuffle mode capability.");
609         }
610
611         /// <summary>
612         /// Sets the content type of latest played media.
613         /// </summary>
614         /// <param name="support">A value indicating whether the <see cref="MediaControlRepeatMode"/> is supported or not.</param>
615         /// <exception cref="InvalidOperationException">
616         ///     The server is not running .<br/>
617         ///     -or-<br/>
618         ///     An internal error occurs.
619         /// </exception>
620         /// <exception cref="ArgumentException"><paramref name="support"/> is invalid.</exception>
621         /// <since_tizen> 5 </since_tizen>
622         public static void SetRepeatModeCapability(MediaControlCapabilitySupport support)
623         {
624             ValidationUtil.ValidateEnum(typeof(MediaControlCapabilitySupport), support, nameof(support));
625
626             Native.SetRepeatModeCapability(Handle, support).ThrowIfError("Failed to set shuffle mode capability.");
627         }
628         #endregion Capabilities
629
630         /// <summary>
631         /// Sets the age rating of latest played media.
632         /// </summary>
633         /// <param name="ageRating">
634         /// The Age rating of latest played media. The valid range is 0 to 19, inclusive.
635         /// Especially, 0 means that media is suitable for all ages.
636         /// </param>
637         /// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="ageRating"/> is not valid.</exception>
638         /// <exception cref="InvalidOperationException">
639         ///     The server is not running .<br/>
640         ///     -or-<br/>
641         ///     An internal error occurs.
642         /// </exception>
643         /// <since_tizen> 5 </since_tizen>
644         public static void SetAgeRating(int ageRating)
645         {
646             if (ageRating < 0 || ageRating > 19)
647             {
648                 throw new ArgumentOutOfRangeException(nameof(ageRating));
649             }
650
651             Native.SetAgeRating(Handle, ageRating).ThrowIfError("Failed to set age rating.");
652
653             Native.UpdatePlayback(Handle).ThrowIfError("Failed to set playback.");
654         }
655     }
656 }