9ef7bd3b64893ccf55bc055189efc2930dc7e6a4
[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 using System;
17 using System.Diagnostics;
18 using System.Threading.Tasks;
19 using Native = Interop.ScreenMirroring;
20
21 namespace Tizen.Multimedia
22 {
23     static internal class ScreenMirroringLog
24     {
25         internal const string LogTag = "Tizen.Multimedia.ScreenMirroring";
26     }
27
28     /// <summary>
29     /// ScreenMirroring class provides methods to function as screen mirroring application as sink.
30     /// It gives the ability to connect to and disconnect from a screen mirroring source, and
31     /// start, pause, and resume the screen mirroring sink, set the resolution or display,
32     /// register state change callback function.
33     /// </summary>
34     public class ScreenMirroring : IDisposable, IDisplayable<int>
35     {
36         internal VideoInformation _videoInfo;
37         internal AudioInformation _audioInfo;
38         internal IntPtr _handle;
39         internal string _ip;
40         internal string _port;
41
42         private bool _disposed = false;
43         private EventHandler<StateChangedEventArgs> _stateChanged;
44         private Native.StateChangedCallback _stateChangedCallback;
45
46         /// <summary>
47         /// Initializes a new instance of the ScreenMirroring class with parameters Ip, Port and Display handle.
48         /// Object should be created only when Ip and Port are available.
49         /// Create(i.e constructor) api will create a new handle with the given parameters.
50         /// </summary>
51         /// <param name="display">Display.</param>
52         /// <param name="ip">Ip.</param>
53         /// <param name="port">Port.</param>
54         /// <exception cref="ArgumentException">Thrown when method fail due to an invalid parameter</exception>
55         public ScreenMirroring(Display display, string ip, string port)
56         {
57             int ret = Native.Create(out _handle);
58             if (ret != (int)ScreenMirroringError.None)
59             {
60                 ScreenMirroringErrorFactory.ThrowException(ret, "Failed to create Screen Mirroring Sink");
61             }
62
63             // initiate values
64             _ip = ip;
65             _port = port;
66
67             // Set ip and port
68             int ret1 = Native.SetIpAndPort(_handle, _ip, _port);
69             if (ret1 != (int)ScreenMirroringError.None)
70             {
71                 Log.Error(ScreenMirroringLog.LogTag, "Set ip and port failed" + (ScreenMirroringError)ret1);
72                 ScreenMirroringErrorFactory.ThrowException(ret, "set ip and port failed");
73             }
74
75             Display = display;
76
77             // AudioInfo
78             _audioInfo = new AudioInformation();
79             _audioInfo._handle = _handle;
80             // VideoInfo
81             _videoInfo = new VideoInformation();
82             _videoInfo._handle = _handle;
83
84             Log.Debug(ScreenMirroringLog.LogTag, "screen mirroring sink created : " + _handle);
85         }
86
87         /// <summary>
88         /// Screen Mirroring destructor.
89         /// </summary>
90         ~ScreenMirroring()
91         {
92             Dispose(false);
93         }
94
95         /// <summary>
96         /// StateChanged event is raised when state change happens.
97         /// Must be called after Create() API.
98         /// </summary>
99         public event EventHandler<StateChangedEventArgs> StateChanged
100         {
101             add
102             {
103                 if (_stateChanged == null)
104                 {
105                     RegisterStateChangedEvent();
106                 }
107
108                 _stateChanged += value;
109             }
110
111             remove
112             {
113                 _stateChanged -= value;
114                 if (_stateChanged == null)
115                 {
116                     UnregisterStateChangedEvent();
117                 }
118             }
119         }
120
121         /// <summary>
122         /// Sets the server ip and port.
123         /// This must be called before connect() and after create().
124         /// </summary>
125         /// <example> If only one handle is used for toggling between more than two source devices,
126         /// then this API ahould be used to assign the parameters to the handle.
127         /// </example>
128         /// <param name="ip">Ip.</param>
129         /// <param name="port">Port.</param>
130         /// <exception cref="ArgumentException">Thrown when method fail due to an invalid parameter</exception>
131         public void SetIpAndPort(string ip, string port)
132         {
133             int ret = Native.SetIpAndPort(_handle, ip, port);
134             if (ret != (int)ScreenMirroringError.None)
135             {
136                 Log.Error(ScreenMirroringLog.LogTag, "Set ip and port failed" + (ScreenMirroringError)ret);
137                 ScreenMirroringErrorFactory.ThrowException(ret, "set ip and port failed");
138             }
139         }
140
141         /// <summary>
142         /// Set Resolution.
143         /// valid state: NULL..
144         /// </summary>
145         /// <param name="resolution"> example: (R1920x1080P30 | R1280x720P30) </param>
146         /// <exception cref="ArgumentException">Thrown when method fail due to an invalid parameter</exception>
147         public void SetResolution(ResolutionType resolution)
148         {
149             int ret = Native.SetResolution(_handle, (int)resolution);
150             if (ret != (int)ScreenMirroringError.None)
151             {
152                 Log.Error(ScreenMirroringLog.LogTag, "Set resolution failed" + (ScreenMirroringError)ret);
153                 ScreenMirroringErrorFactory.ThrowException(ret, "set resolution failed");
154             }
155         }
156
157         private Display _display;
158
159         private int ApplyDisplay(Display display)
160         {
161             return display.ApplyTo(this);
162         }
163
164         private void ReplaceDisplay(Display newDisplay)
165         {
166             if (_display != null)
167             {
168                 _display.Owner = null;
169             }
170             _display = newDisplay;
171             if (_display != null)
172             {
173                 _display.Owner = this;
174             }
175         }
176
177         /// <summary>
178         /// Sets the display.
179         /// This must be called before prepare() and after create().
180         /// </summary>
181         /// <example> If only one handle is used for toggling between more than two source devices,
182         /// then this API should be used to assign the parameters to the handle.
183         /// </example>
184         /// <exception cref="ArgumentException">Thrown when method fail due to an invalid parameter</exception>
185         public Display Display
186         {
187             get
188             {
189                 return _display;
190             }
191             set
192             {
193                 if (value == null)
194                 {
195                     throw new ArgumentNullException(nameof(Display));
196                 }
197
198                 int ret = ApplyDisplay(value);
199                 if (ret != (int)ScreenMirroringError.None)
200                 {
201                     Log.Error(ScreenMirroringLog.LogTag, "Set display failed" + (ScreenMirroringError)ret);
202                     ScreenMirroringErrorFactory.ThrowException(ret, "set display failed");
203                 }
204             }
205         }
206
207         int IDisplayable<int>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
208         {
209             Debug.Assert(_disposed == false);
210
211             Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
212
213             return Native.SetDisplay(_handle, (int)type, evasObject);
214         }
215
216         /// <summary>
217         /// Prepare this instance.
218         /// This must be called after Create().
219         /// </summary>
220         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
221         public void Prepare()
222         {
223             int ret = Native.Prepare(_handle);
224             if (ret != (int)ScreenMirroringError.None)
225             {
226                 ScreenMirroringErrorFactory.ThrowException(ret, "Failed to prepare sink for screen mirroring");
227             }
228         }
229
230         /// <summary>
231         /// Creates connection and prepare for receiving data from ScreenMirroring source.
232         /// This must be called after prepare().
233         /// </summary>
234         /// <remarks> It will not give the current state. Need to subscribe for event to get the current state </remarks>
235         /// <returns>bool value</returns>
236         /// <privilege>http://tizen.org/privilege/internet</privilege>
237         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
238         public Task<bool> ConnectAsync()
239         {
240             int ret = Native.ConnectAsync(_handle);
241             var task = new TaskCompletionSource<bool>();
242
243             Task.Factory.StartNew(() =>
244                 {
245                     if (ret == (int)ScreenMirroringError.None)
246                     {
247                         task.SetResult(true);
248                     }
249
250                     else if (ret != (int)ScreenMirroringError.None)
251                     {
252                         Log.Error(ScreenMirroringLog.LogTag, "Failed to start screen mirroring" + (ScreenMirroringError)ret);
253                         InvalidOperationException e = new InvalidOperationException("Operation Failed");
254                         task.TrySetException(e);
255                     }
256                 });
257
258             return task.Task;
259         }
260
261         /// <summary>
262         /// Get AudioInfo.
263         /// This must be called after connectasync().
264         /// valid states: connected/playback/paused.
265         /// If audio file changes during playback again
266         /// then the current info should be retrieved from the audio information class.
267         /// </summary>
268         /// <value> AudioInfo object </value>
269         public AudioInformation AudioInfo
270         {
271             get
272             {
273                 return _audioInfo;
274             }
275         }
276
277         /// <summary>
278         /// Get VideoInfo.
279         /// This must be called after connectasync().
280         /// valid states: connected/playback/paused.
281         /// If video file changes during playback again
282         /// then the current info should be retrieved from the video information class.
283         /// </summary>
284         /// <value> VideoInfo object </value>
285         public VideoInformation VideoInfo
286         {
287             get
288             {
289                 return _videoInfo;
290             }
291         }
292
293         /// <summary>
294         /// Start receiving data from the ScreenMirroring source and display it(Mirror).
295         /// This must be called after connectasync().
296         ///  </summary>
297         /// <remarks> It will not give the current state. Need to subscribe for event to get the current state </remarks>
298         /// <returns>bool value<returns>
299         /// <privilege>http://tizen.org/privilege/internet</privilege>
300         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
301         public Task<bool> StartAsync()
302         {
303             int ret = Native.StartAsync(_handle);
304             var task = new TaskCompletionSource<bool>();
305
306             Task.Factory.StartNew(() =>
307                 {
308                     if (ret == (int)ScreenMirroringError.None)
309                     {
310                         task.SetResult(true);
311                     }
312
313                     else if (ret != (int)ScreenMirroringError.None)
314                     {
315                         Log.Error(ScreenMirroringLog.LogTag, "Failed to start screen mirroring" + (ScreenMirroringError)ret);
316                         InvalidOperationException e = new InvalidOperationException("Operation Failed");
317                         task.TrySetException(e);
318                     }
319                 });
320
321             return task.Task;
322         }
323
324         /// <summary>
325         /// Pauses receiving data from the ScreenMirroring source.
326         /// This must be called after startasync().
327         /// </summary>
328         /// <remarks> It will not give the current state. Need to subscribe for event to get the current state </remarks>
329         /// <returns>bool value</returns>
330         /// <privilege>http://tizen.org/privilege/internet</privilege>
331         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
332         public Task<bool> PauseAsync()
333         {
334             int ret = Native.PauseAsync(_handle);
335             var task = new TaskCompletionSource<bool>();
336
337             Task.Factory.StartNew(() =>
338                 {
339                     if (ret == (int)ScreenMirroringError.None)
340                     {
341                         task.SetResult(true);
342                     }
343
344                     else if (ret != (int)ScreenMirroringError.None)
345                     {
346                         Log.Error(ScreenMirroringLog.LogTag, "Failed to start screen mirroring" + (ScreenMirroringError)ret);
347                         InvalidOperationException e = new InvalidOperationException("Operation Failed");
348                         task.TrySetException(e);
349                     }
350                 });
351
352             return task.Task;
353         }
354
355         /// <summary>
356         /// Resumes receiving data from the ScreenMirroring source.
357         /// This must be called after pauseasync().
358         /// </summary>
359         /// <remarks> It will not give the current state. Need to subscribe for event to get the current state </remarks>
360         /// <returns>bool value</returns>
361         /// <privilege>http://tizen.org/privilege/internet</privilege>
362         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
363         public Task<bool> ResumeAsync()
364         {
365             int ret = Native.ResumeAsync(_handle);
366             var task = new TaskCompletionSource<bool>();
367
368             Task.Factory.StartNew(() =>
369                 {
370                     if (ret == (int)ScreenMirroringError.None)
371                     {
372                         task.SetResult(true);
373                     }
374
375                     else if (ret != (int)ScreenMirroringError.None)
376                     {
377                         Log.Error(ScreenMirroringLog.LogTag, "Failed to start screen mirroring" + (ScreenMirroringError)ret);
378                         InvalidOperationException e = new InvalidOperationException("Operation Failed");
379                         task.TrySetException(e);
380                     }
381                 });
382
383             return task.Task;
384         }
385
386         /// <summary>
387         /// Disconnect this instance.
388         /// valid states: connected/playing/paused
389         /// </summary>
390         /// <privilege>http://tizen.org/privilege/internet</privilege>
391         /// <exception cref="ArgumentException">Thrown when method fail due to no connection between devices</exception>
392         public void Disconnect()
393         {
394             int ret = Native.Disconnect(_handle);
395             if (ret != (int)ScreenMirroringError.None)
396             {
397                 ScreenMirroringErrorFactory.ThrowException(ret, "Failed to disconnect sink for screen mirroring");
398             }
399         }
400
401         /// <summary>
402         /// Unprepare this instance.
403         /// valid states: prepared/disconnected.
404         /// </summary>
405         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
406         public void Unprepare()
407         {
408             int ret = Native.Unprepare(_handle);
409             if (ret != (int)ScreenMirroringError.None)
410             {
411                 ScreenMirroringErrorFactory.ThrowException(ret, "Failed to reset the screen mirroring sink");
412             }
413         }
414
415         /// <summary>
416         /// Releases all resource used by the <see cref="Tizen.Multimedia.ScreenMirroring"/> object.
417         /// </summary>
418         /// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="Tizen.Multimedia.ScreenMirroring"/>.
419         /// The <see cref="Dispose"/> method leaves the <see cref="Tizen.Multimedia.ScreenMirroring"/> in an unusable
420         /// state. After calling <see cref="Dispose"/>, you must release all references to the
421         /// <see cref="Tizen.Multimedia.ScreenMirroring"/> so the garbage collector can reclaim the memory that the
422         /// <see cref="Tizen.Multimedia.ScreenMirroring"/> was occupying.</remarks>
423         public void Dispose()
424         {
425             Dispose(true);
426             GC.SuppressFinalize(this);
427         }
428
429         /// <summary>
430         /// Dispose the specified handle.
431         /// </summary>
432         /// <param name="disposing">If set to <c>true</c> disposing.</param>
433         protected virtual void Dispose(bool disposing)
434         {
435             if (!_disposed)
436             {
437                 if (disposing)
438                 {
439                     // To be used if there are any other disposable objects
440                 }
441
442                 if (_handle != IntPtr.Zero)
443                 {
444                     Native.Destroy(_handle);
445                     _handle = IntPtr.Zero;
446                 }
447
448                 _disposed = true;
449             }
450         }
451
452         /// <summary>
453         /// Invoke the event for state or error.
454         /// </summary>
455         /// <param name="state"> state </param>
456         /// <param name="error"> error </param>
457         private void StateError(int state, int error)
458         {
459             ///if _stateChanged is subscribe, this will be invoke.
460             StateChangedEventArgs eventArgsState = new StateChangedEventArgs(state, error);
461             _stateChanged?.Invoke(this, eventArgsState);
462         }
463
464         /// <summary>
465         /// Registers the state changed event.
466         /// </summary>
467         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
468         private void RegisterStateChangedEvent()
469         {
470             _stateChangedCallback = (IntPtr userData, int state, int error) =>
471                 {
472                     StateError(state, error);
473                 };
474
475             int ret = Native.SetStateChangedCb(_handle, _stateChangedCallback, IntPtr.Zero);
476             if (ret != (int)ScreenMirroringError.None)
477             {
478                 Log.Error(ScreenMirroringLog.LogTag, "Setting StateChanged callback failed" + (ScreenMirroringError)ret);
479                 ScreenMirroringErrorFactory.ThrowException(ret, "Setting StateChanged callback failed");
480             }
481         }
482
483         /// <summary>
484         /// Unregisters the state changed event.
485         /// </summary>
486         /// <exception cref="InvalidOperationException">Thrown when method fail due to an internal error</exception>
487         private void UnregisterStateChangedEvent()
488         {
489             int ret = Native.UnsetStateChangedCb(_handle);
490             if (ret != (int)ScreenMirroringError.None)
491             {
492                 Log.Error(ScreenMirroringLog.LogTag, "Unsetting StateChnaged callback failed" + (ScreenMirroringError)ret);
493                 ScreenMirroringErrorFactory.ThrowException(ret, "Unsetting StateChanged callback failed");
494             }
495         }
496     }
497 }