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