2 * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Diagnostics;
20 using System.Threading;
21 using System.Threading.Tasks;
22 using Native = Interop.ScreenMirroring;
24 namespace Tizen.Multimedia.Remoting
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.
30 /// <since_tizen> 4 </since_tizen>
31 public class ScreenMirroring : IDisposable, IDisplayable<ScreenMirroringErrorCode>
33 private const string Feature = "http://tizen.org/feature/network.wifi.direct.display";
35 private const uint _port = 2022;
36 private const uint _portMax = 65535;
38 private IntPtr _handle;
40 private AtomicState _state;
42 private bool _disposed = false;
44 internal IntPtr Handle
54 private static bool IsSupported()
56 return System.Information.TryGetValue(Feature, out bool isSupported) ? isSupported : false;
60 /// Initializes a new instance of the ScreenMirroring class.
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()
67 if (IsSupported() == false)
69 throw new PlatformNotSupportedException(
70 $"The feature({Feature}) is not supported on the current device.");
73 Native.Create(out _handle).ThrowIfError("Failed to create ScreenMirroring.");
75 _state = new AtomicState();
77 AudioInfo = new ScreenMirroringAudioInfo(this);
78 VideoInfo = new ScreenMirroringVideoInfo(this);
80 RegisterStateChangedEvent();
84 /// Finalizes an instance of the ScreenMirroring class.
92 /// Occurs when the state is changed.
94 /// <since_tizen> 4 </since_tizen>
95 public event EventHandler<ScreenMirroringStateChangedEventArgs> StateChanged;
98 /// Occurs when an error occurs.
100 /// <since_tizen> 4 </since_tizen>
101 public event EventHandler<ScreenMirroringErrorOccurredEventArgs> ErrorOccurred;
103 #region Display support
105 private Display _display;
107 private void DetachDisplay()
109 if (_display != null)
111 _display.SetOwner(null);
116 private void SetDisplay(Display display)
120 throw new ArgumentNullException(nameof(Display));
123 display.SetOwner(this);
124 display.ApplyTo(this).ThrowIfError("Failed to set display.");
129 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEvasDisplay(DisplayType type,
130 ElmSharp.EvasObject evasObject)
132 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
134 return Native.SetDisplay(Handle, (int)type, evasObject);
137 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEcoreWindow(IntPtr windowHandle)
139 return Native.SetEcoreDisplay(Handle, windowHandle);
144 /// Gets the negotiated audio info.
146 /// <since_tizen> 4 </since_tizen>
147 public ScreenMirroringAudioInfo AudioInfo { get; }
150 /// Gets the negotiated video info.
152 /// <since_tizen> 4 </since_tizen>
153 public ScreenMirroringVideoInfo VideoInfo { get; }
155 private bool IsConnected
159 return _state.IsOneOf(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
160 ScreenMirroringState.Paused);
164 internal void ThrowIfNotConnected()
168 if (IsConnected == false)
170 throw new InvalidOperationException("ScreenMirroring is not connected.");
175 /// Prepares the screen mirroring with the specified display.
178 /// The state must be <see cref="ScreenMirroringState.Idle"/>.<br/>
180 /// All supported resolutions will be candidates.
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.
186 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
187 /// <exception cref="InvalidOperationException">
188 /// The current state is not in the valid.<br/>
190 /// An internal error occurs.
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)
196 PrepareCore(display, (ScreenMirroringResolutions)0);
200 /// Prepares the screen mirroring with the specified display and resolutions.
203 /// The state must be <see cref="ScreenMirroringState.Idle"/>.
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/>
210 /// <paramref name="display"/> has already been assigned to another.
212 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
213 /// <exception cref="InvalidOperationException">
214 /// The current state is not in the valid.<br/>
216 /// An internal error occurs.
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)
222 ValidationUtil.ValidateFlagsEnum(resolutions, (ScreenMirroringResolutions)((1 << 7) - 1), nameof(resolutions));
224 PrepareCore(display, resolutions);
227 private void PrepareCore(Display display, ScreenMirroringResolutions resolutions)
229 ValidateState(ScreenMirroringState.Idle);
231 Native.SetResolution(Handle, resolutions).ThrowIfError("Failed to set resolutions.");
237 Native.Prepare(Handle).ThrowIfError("Failed to prepare.");
246 private Task RunAsync(Func<IntPtr, ScreenMirroringErrorCode> func, string failMessage)
248 var tcs = new TaskCompletionSource<bool>();
252 var ret = func(Handle);
254 if (ret == ScreenMirroringErrorCode.None)
260 tcs.SetException(ret.AsException(failMessage));
268 /// Creates the connection and ready for receiving data from a mirroring source.
270 /// <param name="sourceIp">The source ip address to connect.</param>
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)"/>.
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/>
284 /// An internal error occurs.
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)
292 return ConnectAsync(sourceIp, _port);
296 /// Creates the connection and ready for receiving data from a mirroring source with the given <paramref name="port"/>.
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>
301 /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
302 /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.
304 /// <returns>A task that represents the asynchronous operation.</returns>
305 /// <privilege>http://tizen.org/privilege/internet</privilege>
306 /// <exception cref="ArgumentException"><paramref name="sourceIp"/> is a zero-length string, contains only white space.</exception>
307 /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
308 /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is greater than port max value(65535).</exception>
309 /// <exception cref="InvalidOperationException">
310 /// The current state is not in the valid.<br/>
312 /// An internal error occurs.
314 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
315 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
316 /// <since_tizen> 9 </since_tizen>
317 public Task ConnectAsync(string sourceIp, uint port)
319 if (sourceIp == null)
321 throw new ArgumentNullException(nameof(sourceIp));
323 if (string.IsNullOrWhiteSpace(sourceIp))
325 throw new ArgumentException($"{nameof(sourceIp)} is a zero-length string.", nameof(sourceIp));
329 throw new ArgumentOutOfRangeException(nameof(port), $"{nameof(port)} is greater than max port value(65535).");
332 ValidateState(ScreenMirroringState.Prepared);
334 Native.SetIpAndPort(Handle, sourceIp, port.ToString()).ThrowIfError("Failed to set ip.");
336 return RunAsync(Native.Connect, "Failed to connect.");
340 /// Starts mirroring from the source.
343 /// The state must be <see cref="ScreenMirroringState.Connected"/> state by
344 /// <see cref="ConnectAsync(string)"/>.
346 /// <returns>A task that represents the asynchronous operation.</returns>
347 /// <privilege>http://tizen.org/privilege/internet</privilege>
348 /// <exception cref="InvalidOperationException">
349 /// The current state is not in the valid.<br/>
351 /// An internal error occurs.
353 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
354 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
355 /// <since_tizen> 4 </since_tizen>
356 public Task StartAsync()
358 ValidateState(ScreenMirroringState.Connected);
360 return RunAsync(Native.Start, "Failed to start.");
364 /// Pauses mirroring from the source.
367 /// The state must be <see cref="ScreenMirroringState.Playing"/> state by
368 /// <see cref="StartAsync"/>.
370 /// <returns>A task that represents the asynchronous operation.</returns>
371 /// <privilege>http://tizen.org/privilege/internet</privilege>
372 /// <exception cref="InvalidOperationException">
373 /// The current state is not in the valid.<br/>
375 /// An internal error occurs.
377 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
378 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
379 /// <since_tizen> 4 </since_tizen>
380 public Task PauseAsync()
382 ValidateState(ScreenMirroringState.Playing);
384 return RunAsync(Native.Pause, "Failed to pause.");
388 /// Resumes mirroring from the source.
391 /// The state must be <see cref="ScreenMirroringState.Paused"/> state by
392 /// <see cref="PauseAsync"/>.
394 /// <returns>A task that represents the asynchronous operation.</returns>
395 /// <privilege>http://tizen.org/privilege/internet</privilege>
396 /// <exception cref="InvalidOperationException">
397 /// The current state is not in the valid.<br/>
399 /// An internal error occurs.
401 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
402 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
403 /// <since_tizen> 4 </since_tizen>
404 public Task ResumeAsync()
406 ValidateState(ScreenMirroringState.Paused);
408 return RunAsync(Native.Resume, "Failed to resume.");
412 /// Disconnects from the source.
415 /// The state must be <see cref="ScreenMirroringState.Connected"/>,
416 /// <see cref="ScreenMirroringState.Playing"/> or <see cref="ScreenMirroringState.Paused"/>.
418 /// <privilege>http://tizen.org/privilege/internet</privilege>
419 /// <exception cref="InvalidOperationException">
420 /// The current state is not in the valid.<br/>
422 /// An internal error occurs.
424 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
425 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
426 /// <since_tizen> 4 </since_tizen>
427 public void Disconnect()
429 ValidateState(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
430 ScreenMirroringState.Paused);
432 Native.Disconnect(Handle).ThrowIfError("Failed to disconnect.");
436 /// Unprepares the screen mirroring.
439 /// The state must be <see cref="ScreenMirroringState.Prepared"/>,
440 /// or <see cref="ScreenMirroringState.Disconnected"/>.
442 /// <exception cref="InvalidOperationException">
443 /// The current state is not in the valid.<br/>
445 /// An internal error occurs.
447 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
448 /// <since_tizen> 4 </since_tizen>
449 public void Unprepare()
451 ValidateState(ScreenMirroringState.Prepared, ScreenMirroringState.Disconnected);
453 Native.Unprepare(Handle).ThrowIfError("Failed to reset.");
458 private void ThrowIfDisposed()
462 throw new ObjectDisposedException(nameof(ScreenMirroring));
467 /// Releases all resource used by the <see cref="ScreenMirroring"/> object.
470 /// Call <see cref="Dispose()"/> when you are finished using the <see cref="ScreenMirroring"/>.
471 /// The <see cref="Dispose()"/> method leaves the <see cref="ScreenMirroring"/> in an unusable
472 /// state. After calling <see cref="Dispose()"/>, you must release all references to the
473 /// <see cref="ScreenMirroring"/> so the garbage collector can reclaim the memory that the
474 /// <see cref="ScreenMirroring"/> was occupying.
476 /// <since_tizen> 4 </since_tizen>
477 public void Dispose()
480 GC.SuppressFinalize(this);
484 /// Releases the resources used by the ScreenMirroring.
486 /// <param name="disposing">
487 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
489 /// <since_tizen> 4 </since_tizen>
490 protected virtual void Dispose(bool disposing)
496 if (_handle != IntPtr.Zero)
498 Native.Destroy(_handle);
499 _handle = IntPtr.Zero;
506 private Native.StateChangedCallback _stateChangedCallback;
508 private void RegisterStateChangedEvent()
510 _stateChangedCallback = (error, state, _) =>
512 var prevState = _state.Value;
514 _state.Value = state;
516 if (prevState != state)
518 StateChanged?.Invoke(this, new ScreenMirroringStateChangedEventArgs(state));
522 if (error != ScreenMirroringErrorCode.None)
524 ErrorOccurred?.Invoke(this, new ScreenMirroringErrorOccurredEventArgs(
525 ScreenMirroringError.InvalidOperation));
529 Native.SetStateChangedCb(Handle, _stateChangedCallback).
530 ThrowIfError("Failed to initialize StateChanged event.");
533 private void ValidateState(params ScreenMirroringState[] required)
535 Debug.Assert(required.Length > 0);
539 throw new ObjectDisposedException(nameof(ScreenMirroring));
542 var curState = _state.Value;
543 if (!required.Contains(curState))
545 throw new InvalidOperationException($"The screen mirroring is not in a valid state. " +
546 $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
552 internal class AtomicState
558 _value = (int)ScreenMirroringState.Idle;
561 public ScreenMirroringState Value
565 return (ScreenMirroringState)Interlocked.CompareExchange(ref _value, 0, 0);
569 Interlocked.Exchange(ref _value, (int)value);
573 public bool IsOneOf(params ScreenMirroringState[] states)
575 return states.Contains(Value);