bcd619c2102f5db74e53ba27ce8aa3479f0b77d3
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Remoting / ScreenMirroring / ScreenMirroring.cs
1 /*
2  * Copyright (c) 2016 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.Diagnostics;
19 using System.Linq;
20 using System.Threading;
21 using System.Threading.Tasks;
22 using Native = Interop.ScreenMirroring;
23
24 namespace Tizen.Multimedia.Remoting
25 {
26     /// <summary>
27     /// Provides the ability to connect to and disconnect from a screen mirroring source,
28     /// start, pause, and resume the screen mirroring as a sink.
29     /// </summary>
30     /// <since_tizen> 4 </since_tizen>
31     public class ScreenMirroring : IDisposable, IDisplayable<ScreenMirroringErrorCode>
32     {
33         private const string Feature = "http://tizen.org/feature/network.wifi.direct.display";
34
35         private const uint _port = 2022;
36         private const uint _portMax = 65535;
37
38         private IntPtr _handle;
39
40         private AtomicState _state;
41
42         private bool _disposed = false;
43
44         internal IntPtr Handle
45         {
46             get
47             {
48                 ThrowIfDisposed();
49
50                 return _handle;
51             }
52         }
53
54         private static bool IsSupported()
55         {
56             return System.Information.TryGetValue(Feature, out bool isSupported) ? isSupported : false;
57         }
58
59         /// <summary>
60         /// Initializes a new instance of the ScreenMirroring class.
61         /// </summary>
62         /// <feature>http://tizen.org/feature/network.wifi.direct.display</feature>
63         /// <exception cref="NotSupportedException">The feature is not supported.</exception>
64         /// <since_tizen> 4 </since_tizen>
65         public ScreenMirroring()
66         {
67             if (IsSupported() == false)
68             {
69                 throw new PlatformNotSupportedException(
70                     $"The feature({Feature}) is not supported on the current device.");
71             }
72
73             Native.Create(out _handle).ThrowIfError("Failed to create ScreenMirroring.");
74
75             _state = new AtomicState();
76
77             AudioInfo = new ScreenMirroringAudioInfo(this);
78             VideoInfo = new ScreenMirroringVideoInfo(this);
79
80             RegisterStateChangedEvent();
81         }
82
83         /// <summary>
84         /// Finalizes an instance of the ScreenMirroring class.
85         /// </summary>
86         ~ScreenMirroring()
87         {
88             Dispose(false);
89         }
90
91         /// <summary>
92         /// Occurs when the state is changed.
93         /// </summary>
94         /// <since_tizen> 4 </since_tizen>
95         public event EventHandler<ScreenMirroringStateChangedEventArgs> StateChanged;
96
97         /// <summary>
98         /// Occurs when an error occurs.
99         /// </summary>
100         /// <since_tizen> 4 </since_tizen>
101         public event EventHandler<ScreenMirroringErrorOccurredEventArgs> ErrorOccurred;
102
103         #region Display support
104
105         private Display _display;
106
107         private void DetachDisplay()
108         {
109             if (_display != null)
110             {
111                 _display.SetOwner(null);
112                 _display = null;
113             }
114         }
115
116         private void SetDisplay(Display display)
117         {
118             if (display == null)
119             {
120                 throw new ArgumentNullException(nameof(Display));
121             }
122
123             display.SetOwner(this);
124             display.ApplyTo(this).ThrowIfError("Failed to set display.");
125
126             _display = display;
127         }
128
129         ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEvasDisplay(DisplayType type,
130             ElmSharp.EvasObject evasObject)
131         {
132             Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
133
134             return Native.SetDisplay(Handle, (int)type, evasObject);
135         }
136
137         ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEcoreWindow(IntPtr windowHandle)
138         {
139             return Native.SetEcoreDisplay(Handle, windowHandle);
140         }
141         #endregion
142
143         /// <summary>
144         /// Gets the negotiated audio info.
145         /// </summary>
146         /// <since_tizen> 4 </since_tizen>
147         public ScreenMirroringAudioInfo AudioInfo { get; }
148
149         /// <summary>
150         /// Gets the negotiated video info.
151         /// </summary>
152         /// <since_tizen> 4 </since_tizen>
153         public ScreenMirroringVideoInfo VideoInfo { get; }
154
155         private bool IsConnected
156         {
157             get
158             {
159                 return _state.IsOneOf(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
160                     ScreenMirroringState.Paused);
161             }
162         }
163
164         internal void ThrowIfNotConnected()
165         {
166             ThrowIfDisposed();
167
168             if (IsConnected == false)
169             {
170                 throw new InvalidOperationException("ScreenMirroring is not connected.");
171             }
172         }
173
174         /// <summary>
175         /// Prepares the screen mirroring with the specified display.
176         /// </summary>
177         /// <remarks>
178         /// The state must be <see cref="ScreenMirroringState.Idle"/>.<br/>
179         /// <br/>
180         /// All supported resolutions will be candidates.
181         /// </remarks>
182         /// <param name="display">The display where the mirroring will be played on.</param>
183         /// <exception cref="ArgumentException">
184         ///    <paramref name="display"/> has already been assigned to another.
185         /// </exception>
186         /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
187         /// <exception cref="InvalidOperationException">
188         ///     The current state is not in the valid.<br/>
189         ///     -or-<br/>
190         ///     An internal error occurs.
191         /// </exception>
192         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
193         /// <since_tizen> 4 </since_tizen>
194         public void Prepare(Display display)
195         {
196             PrepareCore(display, (ScreenMirroringResolutions)0);
197         }
198
199         /// <summary>
200         /// Prepares the screen mirroring with the specified display and resolutions.
201         /// </summary>
202         /// <remarks>
203         /// The state must be <see cref="ScreenMirroringState.Idle"/>.
204         /// </remarks>
205         /// <param name="display">The display where the mirroring will be played on.</param>
206         /// <param name="resolutions">The desired resolutions.</param>
207         /// <exception cref="ArgumentException">
208         ///    <paramref name="resolutions"/> contain invalid flags.<br/>
209         ///    -or-<br/>
210         ///    <paramref name="display"/> has already been assigned to another.
211         /// </exception>
212         /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
213         /// <exception cref="InvalidOperationException">
214         ///     The current state is not in the valid.<br/>
215         ///     -or-<br/>
216         ///     An internal error occurs.
217         /// </exception>
218         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
219         /// <since_tizen> 4 </since_tizen>
220         public void Prepare(Display display, ScreenMirroringResolutions resolutions)
221         {
222             ValidationUtil.ValidateFlagsEnum(resolutions, (ScreenMirroringResolutions)((1 << 7) - 1), nameof(resolutions));
223
224             PrepareCore(display, resolutions);
225         }
226
227         private void PrepareCore(Display display, ScreenMirroringResolutions resolutions)
228         {
229             ValidateState(ScreenMirroringState.Idle);
230
231             Native.SetResolution(Handle, resolutions).ThrowIfError("Failed to set resolutions.");
232
233             try
234             {
235                 SetDisplay(display);
236
237                 Native.Prepare(Handle).ThrowIfError("Failed to prepare.");
238             }
239             catch
240             {
241                 DetachDisplay();
242                 throw;
243             }
244         }
245
246         private Task RunAsync(Func<IntPtr, ScreenMirroringErrorCode> func, string failMessage)
247         {
248             var tcs = new TaskCompletionSource<bool>();
249
250             Task.Run(() =>
251             {
252                 var ret = func(Handle);
253
254                 if (ret == ScreenMirroringErrorCode.None)
255                 {
256                     tcs.SetResult(true);
257                 }
258                 else
259                 {
260                     tcs.SetException(ret.AsException(failMessage));
261                 }
262             });
263
264             return tcs.Task;
265         }
266
267         /// <summary>
268         /// Creates the connection and ready for receiving data from a mirroring source.
269         /// </summary>
270         /// <param name="sourceIp">The source ip address to connect.</param>
271         /// <remarks>
272         /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
273         /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.<br/>
274         /// The default port number is 2022.<br/>
275         /// If you want to connect using different port number, please use <see cref="ConnectAsync(string, uint)"/>.
276         /// </remarks>
277         /// <returns>A task that represents the asynchronous operation.</returns>
278         /// <privilege>http://tizen.org/privilege/internet</privilege>
279         /// <exception cref="ArgumentException"><paramref name="sourceIp"/> is a zero-length string, contains only white space.</exception>
280         /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
281         /// <exception cref="InvalidOperationException">
282         ///     The current state is not in the valid.<br/>
283         ///     -or-<br/>
284         ///     An internal error occurs.
285         /// </exception>
286         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
287         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
288         /// <seealso cref="ConnectAsync(string, uint)"/>
289         /// <since_tizen> 4 </since_tizen>
290         public Task ConnectAsync(string sourceIp)
291         {
292             return ConnectAsync(sourceIp, _port);
293         }
294
295         /// <summary>
296         /// Creates the connection and ready for receiving data from a mirroring source with the given <paramref name="port"/>.
297         /// </summary>
298         /// <param name="sourceIp">The source ip address to connect.</param>
299         /// <param name="port">The port number to connect. The max value is 65535.</param>
300         /// <remarks>
301         /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
302         /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.
303         /// </remarks>
304         /// <returns>A task that represents the asynchronous operation.</returns>
305         /// <privilege>http://tizen.org/privilege/internet</privilege>
306         /// <exception cref="ArgumentException">
307         ///     <paramref name="sourceIp"/> is a zero-length string, contains only white space.<br/>
308         ///     -or-<br/>
309         ///     <paramref name="port"/> is greater than port max value(65535).
310         /// </exception>
311         /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
312         /// <exception cref="InvalidOperationException">
313         ///     The current state is not in the valid.<br/>
314         ///     -or-<br/>
315         ///     An internal error occurs.
316         /// </exception>
317         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
318         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
319         /// <since_tizen> 9 </since_tizen>
320         public Task ConnectAsync(string sourceIp, uint port)
321         {
322             if (sourceIp == null)
323             {
324                 throw new ArgumentNullException(nameof(sourceIp));
325             }
326             if (string.IsNullOrWhiteSpace(sourceIp))
327             {
328                 throw new ArgumentException($"{nameof(sourceIp)} is a zero-length string.", nameof(sourceIp));
329             }
330             if (port > _portMax)
331             {
332                 throw new ArgumentException($"{nameof(port)} is greater than max port value(65535).", nameof(port));
333             }
334
335             ValidateState(ScreenMirroringState.Prepared);
336
337             Native.SetIpAndPort(Handle, sourceIp, port.ToString()).ThrowIfError("Failed to set ip.");
338
339             return RunAsync(Native.Connect, "Failed to connect.");
340         }
341
342         /// <summary>
343         /// Starts mirroring from the source.
344         /// </summary>
345         /// <remarks>
346         /// The state must be <see cref="ScreenMirroringState.Connected"/> state by
347         /// <see cref="ConnectAsync(string)"/>.
348         /// </remarks>
349         /// <returns>A task that represents the asynchronous operation.</returns>
350         /// <privilege>http://tizen.org/privilege/internet</privilege>
351         /// <exception cref="InvalidOperationException">
352         ///     The current state is not in the valid.<br/>
353         ///     -or-<br/>
354         ///     An internal error occurs.
355         /// </exception>
356         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
357         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
358         /// <since_tizen> 4 </since_tizen>
359         public Task StartAsync()
360         {
361             ValidateState(ScreenMirroringState.Connected);
362
363             return RunAsync(Native.Start, "Failed to start.");
364         }
365
366         /// <summary>
367         /// Pauses mirroring from the source.
368         /// </summary>
369         /// <remarks>
370         /// The state must be <see cref="ScreenMirroringState.Playing"/> state by
371         /// <see cref="StartAsync"/>.
372         /// </remarks>
373         /// <returns>A task that represents the asynchronous operation.</returns>
374         /// <privilege>http://tizen.org/privilege/internet</privilege>
375         /// <exception cref="InvalidOperationException">
376         ///     The current state is not in the valid.<br/>
377         ///     -or-<br/>
378         ///     An internal error occurs.
379         /// </exception>
380         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
381         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
382         /// <since_tizen> 4 </since_tizen>
383         public Task PauseAsync()
384         {
385             ValidateState(ScreenMirroringState.Playing);
386
387             return RunAsync(Native.Pause, "Failed to pause.");
388         }
389
390         /// <summary>
391         /// Resumes mirroring from the source.
392         /// </summary>
393         /// <remarks>
394         /// The state must be <see cref="ScreenMirroringState.Paused"/> state by
395         /// <see cref="PauseAsync"/>.
396         /// </remarks>
397         /// <returns>A task that represents the asynchronous operation.</returns>
398         /// <privilege>http://tizen.org/privilege/internet</privilege>
399         /// <exception cref="InvalidOperationException">
400         ///     The current state is not in the valid.<br/>
401         ///     -or-<br/>
402         ///     An internal error occurs.
403         /// </exception>
404         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
405         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
406         /// <since_tizen> 4 </since_tizen>
407         public Task ResumeAsync()
408         {
409             ValidateState(ScreenMirroringState.Paused);
410
411             return RunAsync(Native.Resume, "Failed to resume.");
412         }
413
414         /// <summary>
415         /// Disconnects from the source.
416         /// </summary>
417         /// <remarks>
418         /// The state must be <see cref="ScreenMirroringState.Connected"/>,
419         /// <see cref="ScreenMirroringState.Playing"/> or <see cref="ScreenMirroringState.Paused"/>.
420         /// </remarks>
421         /// <privilege>http://tizen.org/privilege/internet</privilege>
422         /// <exception cref="InvalidOperationException">
423         ///     The current state is not in the valid.<br/>
424         ///     -or-<br/>
425         ///     An internal error occurs.
426         /// </exception>
427         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
428         /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
429         /// <since_tizen> 4 </since_tizen>
430         public void Disconnect()
431         {
432             ValidateState(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
433                 ScreenMirroringState.Paused);
434
435             Native.Disconnect(Handle).ThrowIfError("Failed to disconnect.");
436         }
437
438         /// <summary>
439         /// Unprepares the screen mirroring.
440         /// </summary>
441         /// <remarks>
442         /// The state must be <see cref="ScreenMirroringState.Prepared"/>,
443         /// or <see cref="ScreenMirroringState.Disconnected"/>.
444         /// </remarks>
445         /// <exception cref="InvalidOperationException">
446         ///     The current state is not in the valid.<br/>
447         ///     -or-<br/>
448         ///     An internal error occurs.
449         /// </exception>
450         /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
451         /// <since_tizen> 4 </since_tizen>
452         public void Unprepare()
453         {
454             ValidateState(ScreenMirroringState.Prepared, ScreenMirroringState.Disconnected);
455
456             Native.Unprepare(Handle).ThrowIfError("Failed to reset.");
457
458             DetachDisplay();
459         }
460
461         private void ThrowIfDisposed()
462         {
463             if (_disposed)
464             {
465                 throw new ObjectDisposedException(nameof(ScreenMirroring));
466             }
467         }
468
469         /// <summary>
470         /// Releases all resource used by the <see cref="ScreenMirroring"/> object.
471         /// </summary>
472         /// <remarks>
473         /// Call <see cref="Dispose()"/> when you are finished using the <see cref="ScreenMirroring"/>.
474         /// The <see cref="Dispose()"/> method leaves the <see cref="ScreenMirroring"/> in an unusable
475         /// state. After calling <see cref="Dispose()"/>, you must release all references to the
476         /// <see cref="ScreenMirroring"/> so the garbage collector can reclaim the memory that the
477         /// <see cref="ScreenMirroring"/> was occupying.
478         /// </remarks>
479         /// <since_tizen> 4 </since_tizen>
480         public void Dispose()
481         {
482             Dispose(true);
483             GC.SuppressFinalize(this);
484         }
485
486         /// <summary>
487         /// Releases the resources used by the ScreenMirroring.
488         /// </summary>
489         /// <param name="disposing">
490         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
491         /// </param>
492         /// <since_tizen> 4 </since_tizen>
493         protected virtual void Dispose(bool disposing)
494         {
495             if (!_disposed)
496             {
497                 DetachDisplay();
498
499                 if (_handle != IntPtr.Zero)
500                 {
501                     Native.Destroy(_handle);
502                     _handle = IntPtr.Zero;
503                 }
504
505                 _disposed = true;
506             }
507         }
508
509         private Native.StateChangedCallback _stateChangedCallback;
510
511         private void RegisterStateChangedEvent()
512         {
513             _stateChangedCallback = (error, state, _) =>
514             {
515                 var prevState = _state.Value;
516
517                 _state.Value = state;
518
519                 if (prevState != state)
520                 {
521                     StateChanged?.Invoke(this, new ScreenMirroringStateChangedEventArgs(state));
522
523                 }
524
525                 if (error != ScreenMirroringErrorCode.None)
526                 {
527                     ErrorOccurred?.Invoke(this, new ScreenMirroringErrorOccurredEventArgs(
528                         ScreenMirroringError.InvalidOperation));
529                 }
530             };
531
532             Native.SetStateChangedCb(Handle, _stateChangedCallback).
533                 ThrowIfError("Failed to initialize StateChanged event.");
534         }
535
536         private void ValidateState(params ScreenMirroringState[] required)
537         {
538             Debug.Assert(required.Length > 0);
539
540             if (_disposed)
541             {
542                 throw new ObjectDisposedException(nameof(ScreenMirroring));
543             }
544
545             var curState = _state.Value;
546             if (!required.Contains(curState))
547             {
548                 throw new InvalidOperationException($"The screen mirroring is not in a valid state. " +
549                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
550             }
551         }
552
553     }
554
555     internal class AtomicState
556     {
557         private int _value;
558
559         public AtomicState()
560         {
561             _value = (int)ScreenMirroringState.Idle;
562         }
563
564         public ScreenMirroringState Value
565         {
566             get
567             {
568                 return (ScreenMirroringState)Interlocked.CompareExchange(ref _value, 0, 0);
569             }
570             set
571             {
572                 Interlocked.Exchange(ref _value, (int)value);
573             }
574         }
575
576         public bool IsOneOf(params ScreenMirroringState[] states)
577         {
578             return states.Contains(Value);
579         }
580     }
581 }