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