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
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 LogTag = "Tizen.Multimedia.ScreenMirroring";
34 private const int Port = 2022;
36 private ScreenMirroringVideoInfo _videoInfo;
37 private ScreenMirroringAudioInfo _audioInfo;
38 private IntPtr _handle;
40 private AtomicState _state;
42 private bool _disposed = false;
44 internal IntPtr Handle
50 throw new ObjectDisposedException(nameof(ScreenMirroring));
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 public ScreenMirroring()
64 Native.Create(out _handle).ThrowIfError("Failed to create ScreenMirroring.");
66 _state = new AtomicState();
68 _audioInfo = new ScreenMirroringAudioInfo(this);
69 _videoInfo = new ScreenMirroringVideoInfo(this);
71 RegisterStateChangedEvent();
73 Log.Debug(LogTag, "screen mirroring sink created : " + _handle);
82 /// Occurs when the state is changed.
84 public event EventHandler<ScreenMirroringStateChangedEventArgs> StateChanged;
87 /// Occurs when an error occurs.
89 public event EventHandler<ScreenMirroringErrorOccurredEventArgs> ErrorOccurred;
91 #region Display support
93 private Display _display;
95 private void DetachDisplay()
99 _display.SetOwner(null);
104 private void SetDisplay(Display display)
108 throw new ArgumentNullException(nameof(Display));
111 display.SetOwner(this);
112 display.ApplyTo(this).ThrowIfError("Failed to set display.");
117 ScreenMirroringErrorCode IDisplayable<ScreenMirroringErrorCode>.ApplyEvasDisplay(DisplayType type,
118 ElmSharp.EvasObject evasObject)
120 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
122 return Native.SetDisplay(Handle, (int)type, evasObject);
127 /// Gets the negotiated audio info.
129 /// <value>The <see cref="ScreenMirroringAudioInfo"/> if it has been connected, otherwise null.</value>
130 public ScreenMirroringAudioInfo AudioInfo
134 if (IsConnected == false)
144 /// Gets the negotiated video info.
146 /// <value>The <see cref="ScreenMirroringVideoInfo"/> if it has been connected, otherwise null.</value>
147 public ScreenMirroringVideoInfo VideoInfo
151 if (IsConnected == false)
160 private bool IsConnected
164 return _state.IsOneOf(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
165 ScreenMirroringState.Paused);
170 /// Prepares the screen mirroring with the specified display.
173 /// The state must be <see cref="ScreenMirroringState.Idle"/>.\n
175 /// All supported resolutions will be candidates.
177 /// <param name="display">The display where the mirroring will be played on.</param>
178 /// <exception cref="ArgumentException">
179 /// <paramref name="display"/> has already been assigned to another.
181 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
182 /// <exception cref="InvalidOperationException">
183 /// The current state is not in the valid.\n
185 /// An internal error occurs.
187 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
188 public void Prepare(Display display)
190 PrepareCore(display, (ScreenMirroringResolutions)0);
194 /// Prepares the screen mirroring with the specified display and resolutions.
197 /// The state must be <see cref="ScreenMirroringState.Idle"/>.
199 /// <param name="display">The display where the mirroring will be played on.</param>
200 /// <param name="resolutions">The desired resolutions.</param>
201 /// <exception cref="ArgumentException">
202 /// <paramref name="resolutions"/> contain invalid flags.\n
204 /// <paramref name="display"/> has already been assigned to another.
206 /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
207 /// <exception cref="InvalidOperationException">
208 /// The current state is not in the valid.\n
210 /// An internal error occurs.
212 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
213 public void Prepare(Display display, ScreenMirroringResolutions resolutions)
215 ValidationUtil.ValidateFlagsEnum(resolutions, (ScreenMirroringResolutions)((1 << 7) - 1), nameof(resolutions));
217 PrepareCore(display, resolutions);
220 private void PrepareCore(Display display, ScreenMirroringResolutions resolutions)
222 ValidateState(ScreenMirroringState.Idle);
224 Native.SetResolution(Handle, resolutions).ThrowIfError("Failed to set resolutions.");
230 Native.Prepare(Handle).ThrowIfError("Failed to prepare.");
240 /// Creates the connection and ready for receiving data from a mirroring source.
242 /// <param name="sourceIp">The source ip address to connect.</param>
244 /// The state must be <see cref="ScreenMirroringState.Prepared"/> state by
245 /// <see cref="Prepare(Display, ScreenMirroringResolutions)"/>.
247 /// <returns>A task that represents the asynchronous operation.</returns>
248 /// <privilege>http://tizen.org/privilege/internet</privilege>
249 /// <exception cref="ArgumentNullException"><paramref name="sourceIp"/> is null.</exception>
250 /// <exception cref="InvalidOperationException">
251 /// The current state is not in the valid.\n
253 /// An internal error occurs.
255 /// <exception cref="ArgumentException"><paramref name="sourceIp"/> is a zero-length string, contains only white space.</exception>
256 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
257 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
258 public Task ConnectAsync(string sourceIp)
260 if (sourceIp == null)
262 throw new ArgumentNullException(nameof(sourceIp));
265 if (string.IsNullOrWhiteSpace(sourceIp))
267 throw new ArgumentException($"{nameof(sourceIp)} is a zero-length string.", nameof(sourceIp));
270 ValidateState(ScreenMirroringState.Prepared);
272 Native.SetIpAndPort(Handle, sourceIp, Port.ToString()).ThrowIfError("Failed to set ip.");
274 var tcs = new TaskCompletionSource<bool>();
276 Task.Factory.StartNew(() =>
278 Native.Connect(Handle).ThrowIfError("Failed to connect");
286 /// Starts mirroring from the source.
289 /// The state must be <see cref="ScreenMirroringState.Connected"/> state by
290 /// <see cref="ConnectAsync(string)"/>.
292 /// <returns>A task that represents the asynchronous operation.</returns>
293 /// <privilege>http://tizen.org/privilege/internet</privilege>
294 /// <exception cref="InvalidOperationException">
295 /// The current state is not in the valid.\n
297 /// An internal error occurs.
299 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
300 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
301 public Task StartAsync()
303 ValidateState(ScreenMirroringState.Connected);
305 var tcs = new TaskCompletionSource<bool>();
307 Task.Factory.StartNew(() =>
309 Native.StartAsync(Handle).ThrowIfError("Failed to start.");
310 tcs.TrySetResult(true);
317 /// Pauses mirroring from the source.
320 /// The state must be <see cref="ScreenMirroringState.Playing"/> state by
321 /// <see cref="StartAsync"/>.
323 /// <returns>A task that represents the asynchronous operation.</returns>
324 /// <privilege>http://tizen.org/privilege/internet</privilege>
325 /// <exception cref="InvalidOperationException">
326 /// The current state is not in the valid.\n
328 /// An internal error occurs.
330 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
331 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
332 public Task PauseAsync()
334 ValidateState(ScreenMirroringState.Playing);
336 var tcs = new TaskCompletionSource<bool>();
338 Task.Factory.StartNew(() =>
340 Native.PauseAsync(Handle).ThrowIfError("Failed to prepare.");
341 tcs.TrySetResult(true);
348 /// Resumes mirroring from the source.
351 /// The state must be <see cref="ScreenMirroringState.Paused"/> state by
352 /// <see cref="PauseAsync"/>.
354 /// <returns>A task that represents the asynchronous operation.</returns>
355 /// <privilege>http://tizen.org/privilege/internet</privilege>
356 /// <exception cref="InvalidOperationException">
357 /// The current state is not in the valid.\n
359 /// An internal error occurs.
361 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
362 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
363 public Task ResumeAsync()
365 ValidateState(ScreenMirroringState.Paused);
367 var tcs = new TaskCompletionSource<bool>();
369 Task.Factory.StartNew(() =>
371 Native.ResumeAsync(Handle).ThrowIfError("Failed to resume.");
372 tcs.TrySetResult(true);
379 /// Disconnects from the source.
382 /// The state must be <see cref="ScreenMirroringState.Connected"/>,
383 /// <see cref="ScreenMirroringState.Playing"/> or <see cref="ScreenMirroringState.Paused"/>.
385 /// <privilege>http://tizen.org/privilege/internet</privilege>
386 /// <exception cref="InvalidOperationException">
387 /// The current state is not in the valid.\n
389 /// An internal error occurs.
391 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
392 /// <exception cref="UnauthorizedAccessException">Caller does not have required permission.</exception>
393 public void Disconnect()
395 ValidateState(ScreenMirroringState.Connected, ScreenMirroringState.Playing,
396 ScreenMirroringState.Paused);
398 Native.Disconnect(Handle).ThrowIfError("Failed to disconnect.");
402 /// Unprepares the screen mirroring.
405 /// The state must be <see cref="ScreenMirroringState.Prepared"/>,
406 /// or <see cref="ScreenMirroringState.Disconnected"/>.
408 /// <exception cref="InvalidOperationException">
409 /// The current state is not in the valid.\n
411 /// An internal error occurs.
413 /// <exception cref="ObjectDisposedException">The <see cref="ScreenMirroring"/> has already been disposed.</exception>
414 public void Unprepare()
416 ValidateState(ScreenMirroringState.Prepared, ScreenMirroringState.Disconnected);
418 Native.Unprepare(Handle).ThrowIfError("Failed to reset.");
424 /// Releases all resource used by the <see cref="ScreenMirroring"/> object.
427 /// Call <see cref="Dispose()"/> when you are finished using the <see cref="ScreenMirroring"/>.
428 /// The <see cref="Dispose()"/> method leaves the <see cref="ScreenMirroring"/> in an unusable
429 /// state. After calling <see cref="Dispose"()/>, you must release all references to the
430 /// <see cref="ScreenMirroring"/> so the garbage collector can reclaim the memory that the
431 /// <see cref="ScreenMirroring"/> was occupying.
433 public void Dispose()
436 GC.SuppressFinalize(this);
440 /// Releases the resources used by the ScreenMirroring.
442 /// <param name="disposing">
443 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
445 protected virtual void Dispose(bool disposing)
451 if (_handle != IntPtr.Zero)
453 Native.Destroy(_handle);
454 _handle = IntPtr.Zero;
461 private Native.StateChangedCallback _stateChangedCallback;
463 private void RegisterStateChangedEvent()
465 _stateChangedCallback = (_, state, error) =>
467 var prevState = _state.Value;
469 _state.Value = state;
471 if (prevState != state)
473 StateChanged?.Invoke(this, new ScreenMirroringStateChangedEventArgs(state));
477 if (error != ScreenMirroringErrorCode.None)
479 ErrorOccurred?.Invoke(this, new ScreenMirroringErrorOccurredEventArgs(
480 ScreenMirroringError.InvalidOperation));
484 Native.SetStateChangedCb(Handle, _stateChangedCallback).
485 ThrowIfError("Failed to initialize StateChanged event.");
488 private void ValidateState(params ScreenMirroringState[] required)
490 Debug.Assert(required.Length > 0);
494 throw new ObjectDisposedException(nameof(ScreenMirroring));
497 var curState = _state.Value;
498 if (!required.Contains(curState))
500 throw new InvalidOperationException($"The screen mirroring is not in a valid state. " +
501 $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
507 internal class AtomicState
513 _value = (int)ScreenMirroringState.Idle;
516 public ScreenMirroringState Value
520 return (ScreenMirroringState)Interlocked.CompareExchange(ref _value, 0, 0);
524 Interlocked.Exchange(ref _value, (int)value);
528 public bool IsOneOf(params ScreenMirroringState[] states)
530 return states.Contains(Value);