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";
34 private const int Port = 2022;
36 private IntPtr _handle;
38 private AtomicState _state;
40 private bool _disposed = false;
42 internal IntPtr Handle
52 private static bool IsSupported()
54 return System.Information.TryGetValue(Feature, out bool isSupported) ? isSupported : false;
58 /// Initializes a new instance of the ScreenMirroring class.
60 /// <feature>http://tizen.org/feature/network.wifi.direct.display</feature>
61 /// <exception cref="NotSupportedException">The feature is not supported.</exception>
62 /// <since_tizen> 4 </since_tizen>
63 public ScreenMirroring()
65 if (IsSupported() == false)
67 throw new PlatformNotSupportedException(
68 $"The feature({Feature}) is not supported on the current device.");
71 Native.Create(out _handle).ThrowIfError("Failed to create ScreenMirroring.");
73 _state = new AtomicState();
75 AudioInfo = new ScreenMirroringAudioInfo(this);
76 VideoInfo = new ScreenMirroringVideoInfo(this);
78 RegisterStateChangedEvent();
82 /// Finalizes an instance of the ScreenMirroring class.
90 /// Occurs when the state is changed.
92 /// <since_tizen> 4 </since_tizen>
93 public event EventHandler<ScreenMirroringStateChangedEventArgs> StateChanged;
96 /// Occurs when an error occurs.
98 /// <since_tizen> 4 </since_tizen>
99 public event EventHandler<ScreenMirroringErrorOccurredEventArgs> ErrorOccurred;
101 #region Display support
103 private Display _display;
105 private void DetachDisplay()
107 if (_display != null)
109 _display.SetOwner(null);
114 private void SetDisplay(Display display)
118 throw new ArgumentNullException(nameof(Display));
121 display.SetOwner(this);
122 display.ApplyTo(this).ThrowIfError("Failed to set display.");
127 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEvasDisplay(DisplayType type,
128 ElmSharp.EvasObject evasObject)
130 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
132 return Native.SetDisplay(Handle, (int)type, evasObject);
135 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEcoreWindow(IntPtr windowHandle)
137 throw new NotSupportedException("ScreenMirroring does not support NUI.Window display.");
142 /// Gets the negotiated audio info.
144 /// <since_tizen> 4 </since_tizen>
145 public ScreenMirroringAudioInfo AudioInfo { get; }
148 /// Gets the negotiated video info.
150 /// <since_tizen> 4 </since_tizen>
151 public ScreenMirroringVideoInfo VideoInfo { get; }
153 private bool IsConnected
157 return _state.IsOneOf(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
158 ScreenMirroringState.Paused);
162 internal void ThrowIfNotConnected()
166 if (IsConnected == false)
168 throw new InvalidOperationException("ScreenMirroring is not connected.");
173 /// Prepares the screen mirroring with the specified display.
176 /// The state must be <see cref="ScreenMirroringState.Idle"/>.<br/>
178 /// All supported resolutions will be candidates.
180 /// <param name="display">The display where the mirroring will be played on.</param>
181 /// <exception cref="ArgumentException">
182 /// <paramref name="display"/> has already been assigned to another.
184 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
185 /// <exception cref="InvalidOperationException">
186 /// The current state is not in the valid.<br/>
188 /// An internal error occurs.
190 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
191 /// <since_tizen> 4 </since_tizen>
192 public void Prepare(Display display)
194 PrepareCore(display, (ScreenMirroringResolutions)0);
198 /// Prepares the screen mirroring with the specified display and resolutions.
201 /// The state must be <see cref="ScreenMirroringState.Idle"/>.
203 /// <param name="display">The display where the mirroring will be played on.</param>
204 /// <param name="resolutions">The desired resolutions.</param>
205 /// <exception cref="ArgumentException">
206 /// <paramref name="resolutions"/> contain invalid flags.<br/>
208 /// <paramref name="display"/> has already been assigned to another.
210 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
211 /// <exception cref="InvalidOperationException">
212 /// The current state is not in the valid.<br/>
214 /// An internal error occurs.
216 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
217 /// <since_tizen> 4 </since_tizen>
218 public void Prepare(Display display, ScreenMirroringResolutions resolutions)
220 ValidationUtil.ValidateFlagsEnum(resolutions, (ScreenMirroringResolutions)((1 << 7) - 1), nameof(resolutions));
222 PrepareCore(display, resolutions);
225 private void PrepareCore(Display display, ScreenMirroringResolutions resolutions)
227 ValidateState(ScreenMirroringState.Idle);
229 Native.SetResolution(Handle, resolutions).ThrowIfError("Failed to set resolutions.");
235 Native.Prepare(Handle).ThrowIfError("Failed to prepare.");
244 private Task RunAsync(Func<IntPtr, ScreenMirroringErrorCode> func, string failMessage)
246 var tcs = new TaskCompletionSource<bool>();
250 var ret = func(Handle);
252 if (ret == ScreenMirroringErrorCode.None)
258 tcs.SetException(ret.AsException(failMessage));
266 /// Creates the connection and ready for receiving data from a mirroring source.
268 /// <param name="sourceIp">The source ip address to connect.</param>
270 /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
271 /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.
273 /// <returns>A task that represents the asynchronous operation.</returns>
274 /// <privilege>http://tizen.org/privilege/internet</privilege>
275 /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
276 /// <exception cref="InvalidOperationException">
277 /// The current state is not in the valid.<br/>
279 /// An internal error occurs.
281 /// <exception cref="ArgumentException"><paramref name="sourceIp"/> is a zero-length string, contains only white space.</exception>
282 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
283 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
284 /// <since_tizen> 4 </since_tizen>
285 public Task ConnectAsync(string sourceIp)
287 if (sourceIp == null)
289 throw new ArgumentNullException(nameof(sourceIp));
292 if (string.IsNullOrWhiteSpace(sourceIp))
294 throw new ArgumentException($"{nameof(sourceIp)} is a zero-length string.", nameof(sourceIp));
297 ValidateState(ScreenMirroringState.Prepared);
299 Native.SetIpAndPort(Handle, sourceIp, Port.ToString()).ThrowIfError("Failed to set ip.");
301 return RunAsync(Native.Connect, "Failed to connect.");
305 /// Starts mirroring from the source.
308 /// The state must be <see cref="ScreenMirroringState.Connected"/> state by
309 /// <see cref="ConnectAsync(string)"/>.
311 /// <returns>A task that represents the asynchronous operation.</returns>
312 /// <privilege>http://tizen.org/privilege/internet</privilege>
313 /// <exception cref="InvalidOperationException">
314 /// The current state is not in the valid.<br/>
316 /// An internal error occurs.
318 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
319 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
320 /// <since_tizen> 4 </since_tizen>
321 public Task StartAsync()
323 ValidateState(ScreenMirroringState.Connected);
325 return RunAsync(Native.Start, "Failed to start.");
329 /// Pauses mirroring from the source.
332 /// The state must be <see cref="ScreenMirroringState.Playing"/> state by
333 /// <see cref="StartAsync"/>.
335 /// <returns>A task that represents the asynchronous operation.</returns>
336 /// <privilege>http://tizen.org/privilege/internet</privilege>
337 /// <exception cref="InvalidOperationException">
338 /// The current state is not in the valid.<br/>
340 /// An internal error occurs.
342 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
343 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
344 /// <since_tizen> 4 </since_tizen>
345 public Task PauseAsync()
347 ValidateState(ScreenMirroringState.Playing);
349 return RunAsync(Native.Pause, "Failed to pause.");
353 /// Resumes mirroring from the source.
356 /// The state must be <see cref="ScreenMirroringState.Paused"/> state by
357 /// <see cref="PauseAsync"/>.
359 /// <returns>A task that represents the asynchronous operation.</returns>
360 /// <privilege>http://tizen.org/privilege/internet</privilege>
361 /// <exception cref="InvalidOperationException">
362 /// The current state is not in the valid.<br/>
364 /// An internal error occurs.
366 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
367 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
368 /// <since_tizen> 4 </since_tizen>
369 public Task ResumeAsync()
371 ValidateState(ScreenMirroringState.Paused);
373 return RunAsync(Native.Resume, "Failed to resume.");
377 /// Disconnects from the source.
380 /// The state must be <see cref="ScreenMirroringState.Connected"/>,
381 /// <see cref="ScreenMirroringState.Playing"/> or <see cref="ScreenMirroringState.Paused"/>.
383 /// <privilege>http://tizen.org/privilege/internet</privilege>
384 /// <exception cref="InvalidOperationException">
385 /// The current state is not in the valid.<br/>
387 /// An internal error occurs.
389 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
390 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
391 /// <since_tizen> 4 </since_tizen>
392 public void Disconnect()
394 ValidateState(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
395 ScreenMirroringState.Paused);
397 Native.Disconnect(Handle).ThrowIfError("Failed to disconnect.");
401 /// Unprepares the screen mirroring.
404 /// The state must be <see cref="ScreenMirroringState.Prepared"/>,
405 /// or <see cref="ScreenMirroringState.Disconnected"/>.
407 /// <exception cref="InvalidOperationException">
408 /// The current state is not in the valid.<br/>
410 /// An internal error occurs.
412 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
413 /// <since_tizen> 4 </since_tizen>
414 public void Unprepare()
416 ValidateState(ScreenMirroringState.Prepared, ScreenMirroringState.Disconnected);
418 Native.Unprepare(Handle).ThrowIfError("Failed to reset.");
423 private void ThrowIfDisposed()
427 throw new ObjectDisposedException(nameof(ScreenMirroring));
432 /// Releases all resource used by the <see cref="ScreenMirroring"/> object.
435 /// Call <see cref="Dispose()"/> when you are finished using the <see cref="ScreenMirroring"/>.
436 /// The <see cref="Dispose()"/> method leaves the <see cref="ScreenMirroring"/> in an unusable
437 /// state. After calling <see cref="Dispose()"/>, you must release all references to the
438 /// <see cref="ScreenMirroring"/> so the garbage collector can reclaim the memory that the
439 /// <see cref="ScreenMirroring"/> was occupying.
441 /// <since_tizen> 4 </since_tizen>
442 public void Dispose()
445 GC.SuppressFinalize(this);
449 /// Releases the resources used by the ScreenMirroring.
451 /// <param name="disposing">
452 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
454 /// <since_tizen> 4 </since_tizen>
455 protected virtual void Dispose(bool disposing)
461 if (_handle != IntPtr.Zero)
463 Native.Destroy(_handle);
464 _handle = IntPtr.Zero;
471 private Native.StateChangedCallback _stateChangedCallback;
473 private void RegisterStateChangedEvent()
475 _stateChangedCallback = (error, state, _) =>
477 var prevState = _state.Value;
479 _state.Value = state;
481 if (prevState != state)
483 StateChanged?.Invoke(this, new ScreenMirroringStateChangedEventArgs(state));
487 if (error != ScreenMirroringErrorCode.None)
489 ErrorOccurred?.Invoke(this, new ScreenMirroringErrorOccurredEventArgs(
490 ScreenMirroringError.InvalidOperation));
494 Native.SetStateChangedCb(Handle, _stateChangedCallback).
495 ThrowIfError("Failed to initialize StateChanged event.");
498 private void ValidateState(params ScreenMirroringState[] required)
500 Debug.Assert(required.Length > 0);
504 throw new ObjectDisposedException(nameof(ScreenMirroring));
507 var curState = _state.Value;
508 if (!required.Contains(curState))
510 throw new InvalidOperationException($"The screen mirroring is not in a valid state. " +
511 $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
517 internal class AtomicState
523 _value = (int)ScreenMirroringState.Idle;
526 public ScreenMirroringState Value
530 return (ScreenMirroringState)Interlocked.CompareExchange(ref _value, 0, 0);
534 Interlocked.Exchange(ref _value, (int)value);
538 public bool IsOneOf(params ScreenMirroringState[] states)
540 return states.Contains(Value);