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 int Port = 2022;
34 private IntPtr _handle;
36 private AtomicState _state;
38 private bool _disposed = false;
40 internal IntPtr Handle
51 /// Initializes a new instance of the ScreenMirroring class.
53 /// <feature>http://tizen.org/feature/network.wifi.direct.display</feature>
54 /// <exception cref="NotSupportedException">The feature is not supported.</exception>
55 public ScreenMirroring()
57 Native.Create(out _handle).ThrowIfError("Failed to create ScreenMirroring.");
59 _state = new AtomicState();
61 AudioInfo = new ScreenMirroringAudioInfo(this);
62 VideoInfo = new ScreenMirroringVideoInfo(this);
64 RegisterStateChangedEvent();
73 /// Occurs when the state is changed.
75 public event EventHandler<ScreenMirroringStateChangedEventArgs> StateChanged;
78 /// Occurs when an error occurs.
80 public event EventHandler<ScreenMirroringErrorOccurredEventArgs> ErrorOccurred;
82 #region Display support
84 private Display _display;
86 private void DetachDisplay()
90 _display.SetOwner(null);
95 private void SetDisplay(Display display)
99 throw new ArgumentNullException(nameof(Display));
102 display.SetOwner(this);
103 display.ApplyTo(this).ThrowIfError("Failed to set display.");
108 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEvasDisplay(DisplayType type,
109 ElmSharp.EvasObject evasObject)
111 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
113 return Native.SetDisplay(Handle, (int)type, evasObject);
116 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEcoreWindow(IntPtr windowHandle)
118 throw new NotSupportedException("ScreenMirroring does not support NUI.Window display.");
123 /// Gets the negotiated audio info.
125 public ScreenMirroringAudioInfo AudioInfo { get; }
128 /// Gets the negotiated video info.
130 public ScreenMirroringVideoInfo VideoInfo { get; }
132 private bool IsConnected
136 return _state.IsOneOf(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
137 ScreenMirroringState.Paused);
141 internal void ThrowIfNotConnected()
145 if (IsConnected == false)
147 throw new InvalidOperationException("ScreenMirroring is not connected.");
152 /// Prepares the screen mirroring with the specified display.
155 /// The state must be <see cref="ScreenMirroringState.Idle"/>.\n
157 /// All supported resolutions will be candidates.
159 /// <param name="display">The display where the mirroring will be played on.</param>
160 /// <exception cref="ArgumentException">
161 /// <paramref name="display"/> has already been assigned to another.
163 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
164 /// <exception cref="InvalidOperationException">
165 /// The current state is not in the valid.\n
167 /// An internal error occurs.
169 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
170 public void Prepare(Display display)
172 PrepareCore(display, (ScreenMirroringResolutions)0);
176 /// Prepares the screen mirroring with the specified display and resolutions.
179 /// The state must be <see cref="ScreenMirroringState.Idle"/>.
181 /// <param name="display">The display where the mirroring will be played on.</param>
182 /// <param name="resolutions">The desired resolutions.</param>
183 /// <exception cref="ArgumentException">
184 /// <paramref name="resolutions"/> contain invalid flags.\n
186 /// <paramref name="display"/> has already been assigned to another.
188 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
189 /// <exception cref="InvalidOperationException">
190 /// The current state is not in the valid.\n
192 /// An internal error occurs.
194 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
195 public void Prepare(Display display, ScreenMirroringResolutions resolutions)
197 ValidationUtil.ValidateFlagsEnum(resolutions, (ScreenMirroringResolutions)((1 << 7) - 1), nameof(resolutions));
199 PrepareCore(display, resolutions);
202 private void PrepareCore(Display display, ScreenMirroringResolutions resolutions)
204 ValidateState(ScreenMirroringState.Idle);
206 Native.SetResolution(Handle, resolutions).ThrowIfError("Failed to set resolutions.");
212 Native.Prepare(Handle).ThrowIfError("Failed to prepare.");
222 /// Creates the connection and ready for receiving data from a mirroring source.
224 /// <param name="sourceIp">The source ip address to connect.</param>
226 /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
227 /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.
229 /// <returns>A task that represents the asynchronous operation.</returns>
230 /// <privilege>http://tizen.org/privilege/internet</privilege>
231 /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
232 /// <exception cref="InvalidOperationException">
233 /// The current state is not in the valid.\n
235 /// An internal error occurs.
237 /// <exception cref="ArgumentException"><paramref name="sourceIp"/> is a zero-length string, contains only white space.</exception>
238 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
239 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
240 public Task ConnectAsync(string sourceIp)
242 if (sourceIp == null)
244 throw new ArgumentNullException(nameof(sourceIp));
247 if (string.IsNullOrWhiteSpace(sourceIp))
249 throw new ArgumentException($"{nameof(sourceIp)} is a zero-length string.", nameof(sourceIp));
252 ValidateState(ScreenMirroringState.Prepared);
254 Native.SetIpAndPort(Handle, sourceIp, Port.ToString()).ThrowIfError("Failed to set ip.");
256 var tcs = new TaskCompletionSource<bool>();
258 Task.Factory.StartNew(() =>
260 Native.Connect(Handle).ThrowIfError("Failed to connect");
268 /// Starts mirroring from the source.
271 /// The state must be <see cref="ScreenMirroringState.Connected"/> state by
272 /// <see cref="ConnectAsync(string)"/>.
274 /// <returns>A task that represents the asynchronous operation.</returns>
275 /// <privilege>http://tizen.org/privilege/internet</privilege>
276 /// <exception cref="InvalidOperationException">
277 /// The current state is not in the valid.\n
279 /// An internal error occurs.
281 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
282 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
283 public Task StartAsync()
285 ValidateState(ScreenMirroringState.Connected);
287 var tcs = new TaskCompletionSource<bool>();
289 Task.Factory.StartNew(() =>
291 Native.StartAsync(Handle).ThrowIfError("Failed to start.");
292 tcs.TrySetResult(true);
299 /// Pauses mirroring from the source.
302 /// The state must be <see cref="ScreenMirroringState.Playing"/> state by
303 /// <see cref="StartAsync"/>.
305 /// <returns>A task that represents the asynchronous operation.</returns>
306 /// <privilege>http://tizen.org/privilege/internet</privilege>
307 /// <exception cref="InvalidOperationException">
308 /// The current state is not in the valid.\n
310 /// An internal error occurs.
312 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
313 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
314 public Task PauseAsync()
316 ValidateState(ScreenMirroringState.Playing);
318 var tcs = new TaskCompletionSource<bool>();
320 Task.Factory.StartNew(() =>
322 Native.PauseAsync(Handle).ThrowIfError("Failed to prepare.");
323 tcs.TrySetResult(true);
330 /// Resumes mirroring from the source.
333 /// The state must be <see cref="ScreenMirroringState.Paused"/> state by
334 /// <see cref="PauseAsync"/>.
336 /// <returns>A task that represents the asynchronous operation.</returns>
337 /// <privilege>http://tizen.org/privilege/internet</privilege>
338 /// <exception cref="InvalidOperationException">
339 /// The current state is not in the valid.\n
341 /// An internal error occurs.
343 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
344 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
345 public Task ResumeAsync()
347 ValidateState(ScreenMirroringState.Paused);
349 var tcs = new TaskCompletionSource<bool>();
351 Task.Factory.StartNew(() =>
353 Native.ResumeAsync(Handle).ThrowIfError("Failed to resume.");
354 tcs.TrySetResult(true);
361 /// Disconnects from the source.
364 /// The state must be <see cref="ScreenMirroringState.Connected"/>,
365 /// <see cref="ScreenMirroringState.Playing"/> or <see cref="ScreenMirroringState.Paused"/>.
367 /// <privilege>http://tizen.org/privilege/internet</privilege>
368 /// <exception cref="InvalidOperationException">
369 /// The current state is not in the valid.\n
371 /// An internal error occurs.
373 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
374 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
375 public void Disconnect()
377 ValidateState(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
378 ScreenMirroringState.Paused);
380 Native.Disconnect(Handle).ThrowIfError("Failed to disconnect.");
384 /// Unprepares the screen mirroring.
387 /// The state must be <see cref="ScreenMirroringState.Prepared"/>,
388 /// or <see cref="ScreenMirroringState.Disconnected"/>.
390 /// <exception cref="InvalidOperationException">
391 /// The current state is not in the valid.\n
393 /// An internal error occurs.
395 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
396 public void Unprepare()
398 ValidateState(ScreenMirroringState.Prepared, ScreenMirroringState.Disconnected);
400 Native.Unprepare(Handle).ThrowIfError("Failed to reset.");
405 private void ThrowIfDisposed()
409 throw new ObjectDisposedException(nameof(ScreenMirroring));
414 /// Releases all resource used by the <see cref="ScreenMirroring"/> object.
417 /// Call <see cref="Dispose()"/> when you are finished using the <see cref="ScreenMirroring"/>.
418 /// The <see cref="Dispose()"/> method leaves the <see cref="ScreenMirroring"/> in an unusable
419 /// state. After calling <see cref="Dispose"()/>, you must release all references to the
420 /// <see cref="ScreenMirroring"/> so the garbage collector can reclaim the memory that the
421 /// <see cref="ScreenMirroring"/> was occupying.
423 public void Dispose()
426 GC.SuppressFinalize(this);
430 /// Releases the resources used by the ScreenMirroring.
432 /// <param name="disposing">
433 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
435 protected virtual void Dispose(bool disposing)
441 if (_handle != IntPtr.Zero)
443 Native.Destroy(_handle);
444 _handle = IntPtr.Zero;
451 private Native.StateChangedCallback _stateChangedCallback;
453 private void RegisterStateChangedEvent()
455 _stateChangedCallback = (_, state, error) =>
457 var prevState = _state.Value;
459 _state.Value = state;
461 if (prevState != state)
463 StateChanged?.Invoke(this, new ScreenMirroringStateChangedEventArgs(state));
467 if (error != ScreenMirroringErrorCode.None)
469 ErrorOccurred?.Invoke(this, new ScreenMirroringErrorOccurredEventArgs(
470 ScreenMirroringError.InvalidOperation));
474 Native.SetStateChangedCb(Handle, _stateChangedCallback).
475 ThrowIfError("Failed to initialize StateChanged event.");
478 private void ValidateState(params ScreenMirroringState[] required)
480 Debug.Assert(required.Length > 0);
484 throw new ObjectDisposedException(nameof(ScreenMirroring));
487 var curState = _state.Value;
488 if (!required.Contains(curState))
490 throw new InvalidOperationException($"The screen mirroring is not in a valid state. " +
491 $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
497 internal class AtomicState
503 _value = (int)ScreenMirroringState.Idle;
506 public ScreenMirroringState Value
510 return (ScreenMirroringState)Interlocked.CompareExchange(ref _value, 0, 0);
514 Interlocked.Exchange(ref _value, (int)value);
518 public bool IsOneOf(params ScreenMirroringState[] states)
520 return states.Contains(Value);