Release 4.0.0-preview1-00332
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Radio / Radio / Radio.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 using System;
18 using System.Linq;
19 using System.Runtime.InteropServices;
20 using System.Threading.Tasks;
21 using Tizen.System;
22 using static Tizen.Multimedia.Interop.Radio;
23
24 namespace Tizen.Multimedia
25 {
26     /// <summary>
27     /// Provides a means for using the radio feature.
28     /// </summary>
29     public class Radio : IDisposable
30     {
31         private Interop.RadioHandle _handle;
32
33         private const string FeatureFmRadio = "http://tizen.org/feature/fmradio";
34
35         /// <summary>
36         /// Initializes a new instance of the Radio class.
37         /// </summary>
38         /// <exception cref="NotSupportedException">The radio feature is not supported.</exception>
39         public Radio()
40         {
41             ValidateFeatureSupported(FeatureFmRadio);
42
43             Create(out _handle);
44
45             try
46             {
47                 _scanCompletedCallback = _ => ScanCompleted?.Invoke(this, EventArgs.Empty);
48                 _interruptedCallback = (reason, _) => Interrupted?.Invoke(this, new RadioInterruptedEventArgs(reason));
49                 _scanUpdatedCallback = (frequency, _) => ScanUpdated?.Invoke(this, new ScanUpdatedEventArgs(frequency));
50                 _scanStoppedCallback = _ => ScanStopped?.Invoke(this, EventArgs.Empty);
51
52                 SetScanCompletedCb(_handle, _scanCompletedCallback).ThrowIfFailed("Failed to initialize radio");
53                 SetInterruptedCb(_handle, _interruptedCallback).ThrowIfFailed("Failed to initialize radio");
54             }
55             catch (Exception)
56             {
57                 _handle.Dispose();
58                 throw;
59             }
60         }
61
62         private Interop.RadioHandle Handle
63         {
64             get
65             {
66                 if (_disposed)
67                 {
68                     throw new ObjectDisposedException(GetType().Name);
69                 }
70                 return _handle;
71             }
72         }
73
74         private ScanUpdatedCallback _scanUpdatedCallback;
75
76         private ScanStoppedCallback _scanStoppedCallback;
77
78         private ScanCompletedCallback _scanCompletedCallback;
79
80         private InterruptedCallback _interruptedCallback;
81
82         /// <summary>
83         /// Occurs when the radio scanning information is updated.
84         /// </summary>
85         public event EventHandler<ScanUpdatedEventArgs> ScanUpdated;
86
87         /// <summary>
88         /// Occurs when the radio scanning stops.
89         /// </summary>
90         public event EventHandler ScanStopped;
91
92         /// <summary>
93         /// Occurs when the radio scanning is completed.
94         /// </summary>
95         public event EventHandler ScanCompleted;
96
97         /// <summary>
98         /// Occurs when the radio is interrupted.
99         /// </summary>
100         public event EventHandler<RadioInterruptedEventArgs> Interrupted;
101
102         /// <summary>
103         /// Gets the current state of the radio.
104         /// </summary>
105         public RadioState State
106         {
107             get
108             {
109                 RadioState state;
110                 GetState(Handle, out state);
111                 return state;
112             }
113         }
114
115         /// <summary>
116         /// Gets or sets the radio frequency in the range of 87500 ~ 108000 kHz.
117         /// </summary>
118         /// <exception cref="ArgumentOutOfRangeException">
119         ///     <paramref name="value"/> is less than <see cref="Range.Min"/> of <see cref="FrequencyRange"/>.<br/>
120         ///     -or- <br/>
121         ///     <paramref name="value"/> is greater than <see cref="Range.Max"/> of <see cref="FrequencyRange"/>.
122         /// </exception>
123         public int Frequency
124         {
125             get
126             {
127                 int value = 0;
128                 GetFrequency(Handle, out value).ThrowIfFailed("Failed to get frequency");
129                 return value;
130             }
131             set
132             {
133                 if (value < FrequencyRange.Min || value > FrequencyRange.Max)
134                 {
135                     throw new ArgumentOutOfRangeException(nameof(Frequency), value, "Frequency must be within FrequencyRange.");
136                 }
137
138                 SetFrequency(Handle, value).ThrowIfFailed("Failed to set frequency");
139             }
140         }
141
142         /// <summary>
143         /// Gets the current signal strength in the range of -128 ~ 128 dBm.
144         /// </summary>
145         public int SignalStrength
146         {
147             get
148             {
149                 int value = 0;
150                 GetSignalStrength(Handle, out value).ThrowIfFailed("Failed to get signal strength");
151                 return value;
152             }
153         }
154
155         /// <summary>
156         /// Gets or sets the value indicating if the radio is muted.
157         /// </summary>
158         /// <value>
159         /// true if the radio is muted; otherwise, false.
160         /// The default is false.
161         /// </value>
162         public bool IsMuted
163         {
164             get
165             {
166                 bool value;
167                 GetMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state");
168                 return value;
169             }
170             set
171             {
172                 SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state");
173             }
174         }
175
176         /// <summary>
177         /// Gets the channel spacing for the current region.
178         /// </summary>
179         public int ChannelSpacing
180         {
181             get
182             {
183                 int value;
184                 GetChannelSpacing(Handle, out value).ThrowIfFailed("Failed to get channel spacing");
185                 return value;
186             }
187         }
188
189         /// <summary>
190         /// Gets or sets the radio volume level.
191         /// </summary>
192         /// <remarks>Valid volume range is from 0 to 1.0(100%), inclusive.</remarks>
193         /// <value>The default is 1.0.</value>
194         /// <exception cref="ArgumentOutOfRangeException">
195         ///     <paramref name="value"/> is less than zero.<br/>
196         ///     -or-<br/>
197         ///     <paramref name="value"/> is greater than 1.0.
198         /// </exception>
199         public float Volume
200         {
201             get
202             {
203                 float value;
204                 GetVolume(Handle, out value).ThrowIfFailed("Failed to get volume level.");
205                 return value;
206             }
207             set
208             {
209                 if (value < 0F || 1.0F < value)
210                 {
211                     throw new ArgumentOutOfRangeException(nameof(value), value,
212                         $"Valid volume range is 0 <= value <= 1.0, but got { value }.");
213                 }
214
215                 SetVolume(Handle, value).ThrowIfFailed("Failed to set volume level");
216             }
217         }
218
219         /// <summary>
220         /// Gets the frequency for the region in the range of 87500 ~ 108000 kHz.
221         /// </summary>
222         public Range FrequencyRange
223         {
224             get
225             {
226                 int min, max;
227
228                 GetFrequencyRange(Handle, out min, out max).ThrowIfFailed("Failed to get frequency range");
229
230                 return new Range(min, max);
231             }
232         }
233
234         /// <summary>
235         /// Starts the radio.
236         /// </summary>
237         /// <remarks>The radio must be in the <see cref="RadioState.Ready"/> state.</remarks>
238         /// <exception cref="InvalidOperationException">The radio is not in the valid state.</exception>
239         public void Start()
240         {
241             ValidateRadioState(RadioState.Ready);
242
243             Interop.Radio.Start(Handle).ThrowIfFailed("Failed to start radio");
244         }
245
246         /// <summary>
247         /// Stops the radio.
248         /// </summary>
249         /// <remarks>The radio must be in the <see cref="RadioState.Playing"/> state.</remarks>
250         /// <exception cref="InvalidOperationException">The radio is not in the valid state.</exception>
251         public void Stop()
252         {
253             ValidateRadioState(RadioState.Playing);
254
255             Interop.Radio.Stop(Handle).ThrowIfFailed("Failed to stop radio");
256         }
257
258         /// <summary>
259         /// Starts the radio scanning and triggers the <see cref="ScanUpdated"/> event when the scan information is updated.
260         /// </summary>
261         /// <remarks>The radio must be in the <see cref="RadioState.Ready"/> or <see cref="RadioState.Playing"/> state.</remarks>
262         /// <exception cref="InvalidOperationException">The radio is not in the valid state.</exception>
263         /// <seealso cref="ScanUpdated"/>
264         /// <seealso cref="ScanCompleted"/>
265         public void StartScan()
266         {
267             ValidateRadioState(RadioState.Ready, RadioState.Playing);
268
269             ScanStart(Handle, _scanUpdatedCallback).ThrowIfFailed("Failed to start scanning");
270         }
271
272         /// <summary>
273         /// Stops the radio scanning.
274         /// </summary>
275         /// <remarks>The radio must be in the <see cref="RadioState.Scanning"/> state.</remarks>
276         /// <exception cref="InvalidOperationException">The radio is not in the valid state.</exception>
277         /// <seealso cref="ScanStopped"/>
278         public void StopScan()
279         {
280             ValidateRadioState(RadioState.Scanning);
281
282             ScanStop(Handle, _scanStoppedCallback).ThrowIfFailed("Failed to stop scanning");
283         }
284
285         /// <summary>
286         /// Seeks up the effective frequency of the radio.
287         /// </summary>
288         /// <returns>
289         /// A task that represents the asynchronous seeking operation.
290         /// The result value is the current frequency in the range of 87500 ~ 108000 kHz.
291         /// It can be -1 if the seeking operation has failed.
292         /// </returns>
293         /// <remarks>The radio must be in the <see cref="RadioState.Playing"/> state.</remarks>
294         /// <exception cref="InvalidOperationException">
295         ///     The radio is not in the valid state.<br/>
296         ///     -or-<br/>
297         ///     Seeking is in progress.
298         /// </exception>
299         public Task<int> SeekUpAsync()
300         {
301             return SeekAsync(SeekUp);
302         }
303
304         /// <summary>
305         /// Seeks down the effective frequency of the radio.
306         /// </summary>
307         /// <returns>
308         /// A task that represents the asynchronous seeking operation.
309         /// The result value is the current frequency in the range of 87500 ~ 108000 kHz.
310         /// It can be -1 if the seeking operation has failed.
311         /// </returns>
312         /// <remarks>The radio must be in the <see cref="RadioState.Playing"/> state.</remarks>
313         /// <exception cref="InvalidOperationException">
314         ///     The radio is not in the valid state.<br/>
315         ///     -or-<br/>
316         ///     Seeking is in progress.
317         /// </exception>
318         public Task<int> SeekDownAsync()
319         {
320             return SeekAsync(SeekDown);
321         }
322
323         private async Task<int> SeekAsync(Func<Interop.RadioHandle, SeekCompletedCallback, IntPtr, RadioError> func)
324         {
325             ValidateRadioState(RadioState.Playing);
326
327             var tcs = new TaskCompletionSource<int>();
328             SeekCompletedCallback callback = (currentFrequency, _) => tcs.TrySetResult(currentFrequency);
329
330             GCHandle gcHandle;
331             try
332             {
333                 gcHandle = GCHandle.Alloc(callback);
334
335                 func(Handle, callback, IntPtr.Zero).ThrowIfFailed("Failed to seek");
336                 return await tcs.Task;
337             }
338             finally
339             {
340                 gcHandle.Free();
341             }
342         }
343
344         private void ValidateFeatureSupported(string featurePath)
345         {
346             if (Information.TryGetValue(featurePath, out bool supported) == false || supported == false)
347             {
348                 throw new NotSupportedException($"The feature({featurePath}) is not supported.");
349             }
350
351         }
352
353         private void ValidateRadioState(params RadioState[] required)
354         {
355             RadioState curState = State;
356
357             if (required.Contains(curState) == false)
358             {
359                 throw new InvalidOperationException($"{curState} is not valid state.");
360             }
361         }
362
363         #region IDisposable Support
364         private bool _disposed = false;
365
366         /// <summary>
367         /// Releases the resources used by the Radio.
368         /// </summary>
369         /// <param name="disposing">
370         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
371         /// </param>
372         protected virtual void Dispose(bool disposing)
373         {
374             if (!_disposed)
375             {
376                 if (_handle != null)
377                 {
378                     _handle.Dispose();
379                 }
380                 _disposed = true;
381             }
382         }
383
384         /// <summary>
385         /// Releases all resources used by the <see cref="Radio"/> object.
386         /// </summary>
387         public void Dispose()
388         {
389             Dispose(true);
390         }
391         #endregion
392     }
393 }