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