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