[NUI] TCSACR-226 code change (#1032)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Recorder / Recorder / Recorder.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.Diagnostics;
19 using System.Linq;
20 using System.Threading;
21 using Native = Interop.Recorder;
22 using NativeHandle = Interop.RecorderHandle;
23
24 namespace Tizen.Multimedia
25 {
26     /// <summary>
27     /// Recorder is a base class for audio and video recorders that
28     /// provides the ability to control the recording of a multimedia content.<br/>
29     /// <br/>
30     /// Simple audio and audio/video are supported.
31     /// </summary>
32     public abstract partial class Recorder : IDisposable
33     {
34         private readonly NativeHandle _handle;
35         private RecorderState _state;
36         private ThreadLocal<bool> _isInAudioStreamStoring = new ThreadLocal<bool>();
37
38         internal Recorder(NativeHandle handle)
39         {
40             _handle = handle;
41
42             try
43             {
44                 RegisterEvents();
45
46                 SetState(State);
47             }
48             catch (Exception)
49             {
50                 _handle.Dispose();
51                 throw;
52             }
53         }
54
55         internal NativeHandle Handle
56         {
57             get
58             {
59                 if (_disposed)
60                 {
61                     throw new ObjectDisposedException(nameof(Recorder));
62                 }
63
64                 return _handle;
65             }
66         }
67
68         #region Dispose support
69         private bool _disposed;
70
71         /// <summary>
72         /// Releases the unmanaged resources used by the Recorder.
73         /// </summary>
74         /// <since_tizen> 3 </since_tizen>
75         public void Dispose()
76         {
77             Dispose(true);
78         }
79
80         /// <summary>
81         /// Releases the resources used by the Recorder.
82         /// </summary>
83         /// <param name="disposing">
84         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
85         /// </param>
86         /// <since_tizen> 3 </since_tizen>
87         protected virtual void Dispose(bool disposing)
88         {
89             if (!_disposed)
90             {
91                 if (_handle != null)
92                 {
93                     _handle.Dispose();
94                 }
95
96                 _disposed = true;
97             }
98         }
99         #endregion Dispose support
100
101         #region State validation
102         internal void ValidateState(params RecorderState[] required)
103         {
104             Debug.Assert(required.Length > 0);
105
106             var curState = _state;
107             if (!required.Contains(curState))
108             {
109                 throw new InvalidOperationException("The recorder is not in a valid state. " +
110                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
111             }
112         }
113
114         private void SetState(RecorderState state)
115         {
116             _state = state;
117         }
118         #endregion
119
120         #region Properties
121
122         /// <summary>
123         /// Gets the current state of the recorder.
124         /// </summary>
125         /// <value>A <see cref="RecorderState"/> that specifies the state of the recorder.</value>
126         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
127         /// <since_tizen> 3 </since_tizen>
128         public RecorderState State
129         {
130             get
131             {
132                 Native.GetState(Handle, out var val).ThrowIfError("Failed to get recorder state.");
133
134                 return val;
135             }
136         }
137         #endregion Properties
138
139         #region Methods
140         /// <summary>
141         /// Prepares the media recorder for recording.
142         /// </summary>
143         /// <remarks>
144         /// The recorder should be in the <see cref="RecorderState.Idle"/> state.
145         /// The state of the recorder will be the <see cref="RecorderState.Ready"/> after this.
146         /// It has no effect if the current state is the <see cref="RecorderState.Ready"/>.
147         /// </remarks>
148         /// <exception cref="InvalidOperationException">
149         ///     The recorder is not in the valid state.<br/>
150         ///     -or-<br/>
151         ///     An internal error occurred.
152         /// </exception>
153         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
154         /// <since_tizen> 3 </since_tizen>
155         public void Prepare()
156         {
157             if (_state == RecorderState.Ready)
158             {
159                 return;
160             }
161
162             ValidateState(RecorderState.Idle);
163
164             Native.Prepare(Handle).ThrowIfError("Failed to prepare media recorder");
165
166             SetState(RecorderState.Ready);
167         }
168
169         private void ThrowIfAccessedInAudioStreamStoring()
170         {
171             if (_isInAudioStreamStoring.Value)
172             {
173                 throw new InvalidOperationException("The method can't be called in the AudioStreamStoring event");
174             }
175         }
176
177         /// <summary>
178         /// Resets the media recorder.
179         /// </summary>
180         /// <remarks>
181         /// The recorder should be in the <see cref="RecorderState.Ready"/> state.
182         /// The state of recorder will be the <see cref="RecorderState.Idle"/> after this.
183         /// It has no effect if the current state is the <see cref="RecorderState.Idle"/>.
184         /// </remarks>
185         /// <exception cref="InvalidOperationException">
186         ///     The recorder is not in the valid state.<br/>
187         ///     -or-<br/>
188         ///     An internal error occurred.
189         /// </exception>
190         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
191         /// <since_tizen> 3 </since_tizen>
192         public void Unprepare()
193         {
194             ThrowIfAccessedInAudioStreamStoring();
195
196             if (_state == RecorderState.Idle)
197             {
198                 return;
199             }
200
201             ValidateState(RecorderState.Ready);
202
203             Native.Unprepare(Handle).ThrowIfError("Failed to reset the media recorder");
204
205             SetState(RecorderState.Idle);
206         }
207
208         /// <summary>
209         /// Starts the recording.
210         /// </summary>
211         /// <remarks>
212         /// The recorder must be in the <see cref="RecorderState.Ready"/> state.
213         /// The state of the recorder will be the <see cref="RecorderState.Recording"/> after this. <br/>
214         /// <br/>
215         /// If the specified path exists, the file is removed automatically and updated by new one.<br/>
216         /// The mediastorage privilege(http://tizen.org/privilege/mediastorage) is required if the path is relevant to media storage.<br/>
217         /// The externalstorage privilege(http://tizen.org/privilege/externalstorage) is required if the path is relevant to external storage.<br/>
218         /// <br/>
219         /// In the video recorder, some preview format does not support record mode.
220         /// You should use the default preview format or the <see cref="CameraPixelFormat.Nv12"/> in the record mode.
221         /// </remarks>
222         /// <param name="savePath">The file path for recording result.</param>
223         /// <privilege>http://tizen.org/privilege/recorder</privilege>
224         /// <exception cref="InvalidOperationException">
225         ///     The recorder is not in the valid state.<br/>
226         ///     -or-<br/>
227         ///     The preview format of the camera is not supported.<br/>
228         ///     -or-<br/>
229         ///     An internal error occurred.
230         /// </exception>
231         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
232         /// <exception cref="ArgumentNullException"><paramref name="savePath"/> is null.</exception>
233         /// <exception cref="ArgumentException"><paramref name="savePath"/> is a zero-length string, contains only white space.</exception>
234         /// <exception cref="UnauthorizedAccessException">Caller does not have required privilege.</exception>
235         /// <seealso cref="Commit"/>
236         /// <seealso cref="Cancel"/>
237         /// <since_tizen> 4 </since_tizen>
238         public void Start(string savePath)
239         {
240             ValidateState(RecorderState.Ready);
241
242             if (savePath == null)
243             {
244                 throw new ArgumentNullException(nameof(savePath));
245             }
246
247             if (string.IsNullOrWhiteSpace(savePath))
248             {
249                 throw new ArgumentException($"{nameof(savePath)} is an empty string.", nameof(savePath));
250             }
251
252             Native.SetFileName(Handle, savePath).ThrowIfError("Failed to set save path.");
253
254             Native.Start(Handle).ThrowIfError("Failed to start the media recorder");
255
256             SetState(RecorderState.Recording);
257         }
258
259         /// <summary>
260         /// Resumes the recording.
261         /// </summary>
262         /// <remarks>
263         /// The recorder should be in the <see cref="RecorderState.Paused"/> state.
264         /// The state of recorder will be the <see cref="RecorderState.Recording"/> after this.
265         /// It has no effect if the current state is the <see cref="RecorderState.Recording"/>.
266         /// </remarks>
267         /// <exception cref="InvalidOperationException">
268         ///     The recorder is not in the valid state.<br/>
269         ///     -or-<br/>
270         ///     An internal error occurred.
271         /// </exception>
272         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
273         /// <since_tizen> 4 </since_tizen>
274         public void Resume()
275         {
276             if (_state == RecorderState.Recording)
277             {
278                 return;
279             }
280
281             ValidateState(RecorderState.Paused);
282
283             Native.Start(Handle).ThrowIfError("Failed to resume the media recorder");
284
285             SetState(RecorderState.Recording);
286         }
287
288         /// <summary>
289         /// Pauses the recording.
290         /// </summary>
291         /// <remarks>
292         /// The recorder should be in the <see cref="RecorderState.Recording"/> state.
293         /// The state of the recorder will be the <see cref="RecorderState.Paused"/> after this.
294         /// It has no effect if the current state is the <see cref="RecorderState.Paused"/>.
295         /// </remarks>
296         /// <exception cref="InvalidOperationException">
297         ///     The recorder is not in the valid state.<br/>
298         ///     -or-<br/>
299         ///     An internal error occurred.
300         /// </exception>
301         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
302         /// <since_tizen> 3 </since_tizen>
303         public void Pause()
304         {
305             if (_state == RecorderState.Paused)
306             {
307                 return;
308             }
309
310             ValidateState(RecorderState.Recording);
311
312             Native.Pause(Handle).ThrowIfError("Failed to pause the media recorder");
313
314             SetState(RecorderState.Paused);
315         }
316
317         /// <summary>
318         /// Stops recording and saves the result.
319         /// </summary>
320         /// <remarks>
321         /// The recorder must be in the <see cref="RecorderState.Recording"/> or the <see cref="RecorderState.Paused"/> state.
322         /// The state of the recorder will be the <see cref="RecorderState.Ready"/> after the operation.
323         /// </remarks>
324         /// <exception cref="InvalidOperationException">
325         ///     The recorder is not in the valid state.<br/>
326         ///     -or-<br/>
327         ///     The method is called in <see cref="AudioStreamStoring"/> event.<br/>
328         ///     -or-<br/>
329         ///     An internal error occurred.
330         /// </exception>
331         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
332         /// <since_tizen> 3 </since_tizen>
333         public void Commit()
334         {
335             ThrowIfAccessedInAudioStreamStoring();
336
337             ValidateState(RecorderState.Recording, RecorderState.Paused);
338
339             Native.Commit(Handle).ThrowIfError("Failed to save the recorded content");
340
341             SetState(RecorderState.Ready);
342         }
343
344         /// <summary>
345         /// Cancels the recording.<br/>
346         /// The recording data is discarded and not written in the recording file.
347         /// </summary>
348         /// <remarks>
349         /// The recorder must be in the <see cref="RecorderState.Recording"/> or the <see cref="RecorderState.Paused"/> state.
350         /// The state of the recorder will be the <see cref="RecorderState.Ready"/> after the operation.
351         /// </remarks>
352         /// <exception cref="InvalidOperationException">
353         ///     The recorder is not in the valid state.<br/>
354         ///     -or-<br/>
355         ///     The method is called in <see cref="AudioStreamStoring"/> event.<br/>
356         ///     -or-<br/>
357         ///     An internal error occurred.
358         /// </exception>
359         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
360         /// <since_tizen> 3 </since_tizen>
361         public void Cancel()
362         {
363             ThrowIfAccessedInAudioStreamStoring();
364
365             ValidateState(RecorderState.Recording, RecorderState.Paused);
366
367             Native.Cancel(Handle).ThrowIfError("Failed to cancel the recording");
368
369             SetState(RecorderState.Ready);
370         }
371
372         /// <summary>
373         /// Apply the audio stream policy.
374         /// </summary>
375         /// <remarks>
376         /// The recorder must be in the <see cref="RecorderState.Idle"/> or the <see cref="RecorderState.Ready"/> state.
377         /// </remarks>
378         /// <param name="policy">The policy to apply.</param>
379         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
380         /// <exception cref="InvalidOperationException">
381         ///     The recorder is not in the valid state.<br/>
382         ///     -or-<br/>
383         ///     <paramref name="policy"/> is not supported for the recorder.<br/>
384         ///     -or-<br/>
385         ///     An internal error occurred.
386         /// </exception>
387         /// <exception cref="ObjectDisposedException">
388         ///     The recorder already has been disposed of.<br/>
389         ///     -or-<br/>
390         ///     <paramref name="policy"/> already has been disposed of.
391         /// </exception>
392         /// <since_tizen> 4 </since_tizen>
393         public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
394         {
395             if (policy == null)
396             {
397                 throw new ArgumentNullException(nameof(policy));
398             }
399
400             ValidateState(RecorderState.Idle, RecorderState.Ready);
401
402             Native.SetAudioStreamPolicy(Handle, policy.Handle).ThrowIfError("Failed to apply the audio stream policy.");
403         }
404
405         /// <summary>
406         /// Returns the peak audio input level in dB since the last call to this method.
407         /// </summary>
408         /// <remarks>
409         /// 0dB indicates the maximum input level, -300dB indicates the minimum input level.<br/>
410         /// <br/>
411         /// The recorder must be in the <see cref="RecorderState.Recording"/> or the <see cref="RecorderState.Paused"/> state.
412         /// </remarks>
413         /// <exception cref="ObjectDisposedException">The recorder already has been disposed of.</exception>
414         /// <since_tizen> 4 </since_tizen>
415         public double GetPeakAudioLevel()
416         {
417             ValidateState(RecorderState.Recording, RecorderState.Paused);
418
419             Native.GetAudioLevel(Handle, out var level).ThrowIfError("Failed to get audio level.");
420
421             return level;
422         }
423
424         /// <summary>
425         /// Returns the state of recorder device.
426         /// </summary>
427         /// <exception cref="ArgumentException"><paramref name="type"/> is invalid.</exception>
428         /// <since_tizen> 4 </since_tizen>
429         public static RecorderDeviceState GetDeviceState(RecorderType type)
430         {
431             ValidationUtil.ValidateEnum(typeof(RecorderType), type, nameof(type));
432
433             Native.GetDeviceState(type, out var state).ThrowIfError("Failed to get device state");
434
435             return state;
436         }
437         #endregion
438     }
439 }