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 public class ScreenMirroring : IDisposable, IDisplayable<ScreenMirroringErrorCode>
32 private const string Feature = "http://tizen.org/feature/network.wifi.direct.display";
33 private const int Port = 2022;
35 private IntPtr _handle;
37 private AtomicState _state;
39 private bool _disposed = false;
41 internal IntPtr Handle
51 private static bool IsSupported()
53 return System.Information.TryGetValue(Feature, out bool isSupported) ? isSupported : false;
57 /// Initializes a new instance of the ScreenMirroring class.
59 /// <feature>http://tizen.org/feature/network.wifi.direct.display</feature>
60 /// <exception cref="NotSupportedException">The feature is not supported.</exception>
61 public ScreenMirroring()
63 if (IsSupported() == false)
65 throw new PlatformNotSupportedException($"The feature({Feature}) is not supported on the current device");
68 Native.Create(out _handle).ThrowIfError("Failed to create ScreenMirroring.");
70 _state = new AtomicState();
72 AudioInfo = new ScreenMirroringAudioInfo(this);
73 VideoInfo = new ScreenMirroringVideoInfo(this);
75 RegisterStateChangedEvent();
79 /// Finalizes an instance of the ScreenMirroring class.
87 /// Occurs when the state is changed.
89 public event EventHandler<ScreenMirroringStateChangedEventArgs> StateChanged;
92 /// Occurs when an error occurs.
94 public event EventHandler<ScreenMirroringErrorOccurredEventArgs> ErrorOccurred;
96 #region Display support
98 private Display _display;
100 private void DetachDisplay()
102 if (_display != null)
104 _display.SetOwner(null);
109 private void SetDisplay(Display display)
113 throw new ArgumentNullException(nameof(Display));
116 display.SetOwner(this);
117 display.ApplyTo(this).ThrowIfError("Failed to set display.");
122 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEvasDisplay(DisplayType type,
123 ElmSharp.EvasObject evasObject)
125 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
127 return Native.SetDisplay(Handle, (int)type, evasObject);
130 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEcoreWindow(IntPtr windowHandle)
132 throw new NotSupportedException("ScreenMirroring does not support NUI.Window display.");
137 /// Gets the negotiated audio info.
139 public ScreenMirroringAudioInfo AudioInfo { get; }
142 /// Gets the negotiated video info.
144 public ScreenMirroringVideoInfo VideoInfo { get; }
146 private bool IsConnected
150 return _state.IsOneOf(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
151 ScreenMirroringState.Paused);
155 internal void ThrowIfNotConnected()
159 if (IsConnected == false)
161 throw new InvalidOperationException("ScreenMirroring is not connected.");
166 /// Prepares the screen mirroring with the specified display.
169 /// The state must be <see cref="ScreenMirroringState.Idle"/>.<br/>
171 /// All supported resolutions will be candidates.
173 /// <param name="display">The display where the mirroring will be played on.</param>
174 /// <exception cref="ArgumentException">
175 /// <paramref name="display"/> has already been assigned to another.
177 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
178 /// <exception cref="InvalidOperationException">
179 /// The current state is not in the valid.<br/>
181 /// An internal error occurs.
183 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
184 public void Prepare(Display display)
186 PrepareCore(display, (ScreenMirroringResolutions)0);
190 /// Prepares the screen mirroring with the specified display and resolutions.
193 /// The state must be <see cref="ScreenMirroringState.Idle"/>.
195 /// <param name="display">The display where the mirroring will be played on.</param>
196 /// <param name="resolutions">The desired resolutions.</param>
197 /// <exception cref="ArgumentException">
198 /// <paramref name="resolutions"/> contain invalid flags.<br/>
200 /// <paramref name="display"/> has already been assigned to another.
202 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
203 /// <exception cref="InvalidOperationException">
204 /// The current state is not in the valid.<br/>
206 /// An internal error occurs.
208 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
209 public void Prepare(Display display, ScreenMirroringResolutions resolutions)
211 ValidationUtil.ValidateFlagsEnum(resolutions, (ScreenMirroringResolutions)((1 << 7) - 1), nameof(resolutions));
213 PrepareCore(display, resolutions);
216 private void PrepareCore(Display display, ScreenMirroringResolutions resolutions)
218 ValidateState(ScreenMirroringState.Idle);
220 Native.SetResolution(Handle, resolutions).ThrowIfError("Failed to set resolutions.");
226 Native.Prepare(Handle).ThrowIfError("Failed to prepare.");
236 /// Creates the connection and ready for receiving data from a mirroring source.
238 /// <param name="sourceIp">The source ip address to connect.</param>
240 /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
241 /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.
243 /// <returns>A task that represents the asynchronous operation.</returns>
244 /// <privilege>http://tizen.org/privilege/internet</privilege>
245 /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
246 /// <exception cref="InvalidOperationException">
247 /// The current state is not in the valid.<br/>
249 /// An internal error occurs.
251 /// <exception cref="ArgumentException"><paramref name="sourceIp"/> is a zero-length string, contains only white space.</exception>
252 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
253 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
254 public Task ConnectAsync(string sourceIp)
256 if (sourceIp == null)
258 throw new ArgumentNullException(nameof(sourceIp));
261 if (string.IsNullOrWhiteSpace(sourceIp))
263 throw new ArgumentException($"{nameof(sourceIp)} is a zero-length string.", nameof(sourceIp));
266 ValidateState(ScreenMirroringState.Prepared);
268 Native.SetIpAndPort(Handle, sourceIp, Port.ToString()).ThrowIfError("Failed to set ip.");
270 var tcs = new TaskCompletionSource<bool>();
272 Task.Factory.StartNew(() =>
274 Native.Connect(Handle).ThrowIfError("Failed to connect");
282 /// Starts mirroring from the source.
285 /// The state must be <see cref="ScreenMirroringState.Connected"/> state by
286 /// <see cref="ConnectAsync(string)"/>.
288 /// <returns>A task that represents the asynchronous operation.</returns>
289 /// <privilege>http://tizen.org/privilege/internet</privilege>
290 /// <exception cref="InvalidOperationException">
291 /// The current state is not in the valid.<br/>
293 /// An internal error occurs.
295 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
296 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
297 public Task StartAsync()
299 ValidateState(ScreenMirroringState.Connected);
301 var tcs = new TaskCompletionSource<bool>();
303 Task.Factory.StartNew(() =>
305 Native.StartAsync(Handle).ThrowIfError("Failed to start.");
306 tcs.TrySetResult(true);
313 /// Pauses mirroring from the source.
316 /// The state must be <see cref="ScreenMirroringState.Playing"/> state by
317 /// <see cref="StartAsync"/>.
319 /// <returns>A task that represents the asynchronous operation.</returns>
320 /// <privilege>http://tizen.org/privilege/internet</privilege>
321 /// <exception cref="InvalidOperationException">
322 /// The current state is not in the valid.<br/>
324 /// An internal error occurs.
326 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
327 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
328 public Task PauseAsync()
330 ValidateState(ScreenMirroringState.Playing);
332 var tcs = new TaskCompletionSource<bool>();
334 Task.Factory.StartNew(() =>
336 Native.PauseAsync(Handle).ThrowIfError("Failed to prepare.");
337 tcs.TrySetResult(true);
344 /// Resumes mirroring from the source.
347 /// The state must be <see cref="ScreenMirroringState.Paused"/> state by
348 /// <see cref="PauseAsync"/>.
350 /// <returns>A task that represents the asynchronous operation.</returns>
351 /// <privilege>http://tizen.org/privilege/internet</privilege>
352 /// <exception cref="InvalidOperationException">
353 /// The current state is not in the valid.<br/>
355 /// An internal error occurs.
357 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
358 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
359 public Task ResumeAsync()
361 ValidateState(ScreenMirroringState.Paused);
363 var tcs = new TaskCompletionSource<bool>();
365 Task.Factory.StartNew(() =>
367 Native.ResumeAsync(Handle).ThrowIfError("Failed to resume.");
368 tcs.TrySetResult(true);
375 /// Disconnects from the source.
378 /// The state must be <see cref="ScreenMirroringState.Connected"/>,
379 /// <see cref="ScreenMirroringState.Playing"/> or <see cref="ScreenMirroringState.Paused"/>.
381 /// <privilege>http://tizen.org/privilege/internet</privilege>
382 /// <exception cref="InvalidOperationException">
383 /// The current state is not in the valid.<br/>
385 /// An internal error occurs.
387 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
388 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
389 public void Disconnect()
391 ValidateState(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
392 ScreenMirroringState.Paused);
394 Native.Disconnect(Handle).ThrowIfError("Failed to disconnect.");
398 /// Unprepares the screen mirroring.
401 /// The state must be <see cref="ScreenMirroringState.Prepared"/>,
402 /// or <see cref="ScreenMirroringState.Disconnected"/>.
404 /// <exception cref="InvalidOperationException">
405 /// The current state is not in the valid.<br/>
407 /// An internal error occurs.
409 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
410 public void Unprepare()
412 ValidateState(ScreenMirroringState.Prepared, ScreenMirroringState.Disconnected);
414 Native.Unprepare(Handle).ThrowIfError("Failed to reset.");
419 private void ThrowIfDisposed()
423 throw new ObjectDisposedException(nameof(ScreenMirroring));
428 /// Releases all resource used by the <see cref="ScreenMirroring"/> object.
431 /// Call <see cref="Dispose()"/> when you are finished using the <see cref="ScreenMirroring"/>.
432 /// The <see cref="Dispose()"/> method leaves the <see cref="ScreenMirroring"/> in an unusable
433 /// state. After calling <see cref="Dispose()"/>, you must release all references to the
434 /// <see cref="ScreenMirroring"/> so the garbage collector can reclaim the memory that the
435 /// <see cref="ScreenMirroring"/> was occupying.
437 public void Dispose()
440 GC.SuppressFinalize(this);
444 /// Releases the resources used by the ScreenMirroring.
446 /// <param name="disposing">
447 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
449 protected virtual void Dispose(bool disposing)
455 if (_handle != IntPtr.Zero)
457 Native.Destroy(_handle);
458 _handle = IntPtr.Zero;
465 private Native.StateChangedCallback _stateChangedCallback;
467 private void RegisterStateChangedEvent()
469 _stateChangedCallback = (error, state, _) =>
471 var prevState = _state.Value;
473 _state.Value = state;
475 if (prevState != state)
477 StateChanged?.Invoke(this, new ScreenMirroringStateChangedEventArgs(state));
481 if (error != ScreenMirroringErrorCode.None)
483 ErrorOccurred?.Invoke(this, new ScreenMirroringErrorOccurredEventArgs(
484 ScreenMirroringError.InvalidOperation));
488 Native.SetStateChangedCb(Handle, _stateChangedCallback).
489 ThrowIfError("Failed to initialize StateChanged event.");
492 private void ValidateState(params ScreenMirroringState[] required)
494 Debug.Assert(required.Length > 0);
498 throw new ObjectDisposedException(nameof(ScreenMirroring));
501 var curState = _state.Value;
502 if (!required.Contains(curState))
504 throw new InvalidOperationException($"The screen mirroring is not in a valid state. " +
505 $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
511 internal class AtomicState
517 _value = (int)ScreenMirroringState.Idle;
520 public ScreenMirroringState Value
524 return (ScreenMirroringState)Interlocked.CompareExchange(ref _value, 0, 0);
528 Interlocked.Exchange(ref _value, (int)value);
532 public bool IsOneOf(params ScreenMirroringState[] states)
534 return states.Contains(Value);