[TCSACR-37][Camera/Recorder] Modify camera, recorder privilege for some APIs
[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 Native = Interop.Recorder;
21
22 namespace Tizen.Multimedia
23 {
24     static internal class RecorderLog
25     {
26         internal const string Tag = "Tizen.Multimedia.Recorder";
27     }
28
29     /// <summary>
30     /// The recorder class provides methods to create audio/video recorder,
31     ///  to start, stop and save the recorded content. It also provides methods
32     ///  to get/set various attributes and capabilities of recorder.
33     /// </summary>
34     public class Recorder : IDisposable
35     {
36         private IntPtr _handle = IntPtr.Zero;
37         private bool _disposed = false;
38         private RecorderState _state = RecorderState.None;
39
40         /// <summary>
41         /// Audio recorder constructor.
42         /// </summary>
43         public Recorder()
44         {
45             RecorderErrorFactory.ThrowIfError(Native.Create(out _handle),
46                 "Failed to create Audio recorder");
47
48             Feature = new RecorderFeatures(this);
49             Setting = new RecorderSettings(this);
50
51             RegisterCallbacks();
52
53             SetState(RecorderState.Created);
54         }
55
56         /// <summary>
57         /// Video recorder constructor.
58         /// </summary>
59         /// <param name="camera">
60         /// The camera object.
61         /// </param>
62         public Recorder(Camera camera)
63         {
64             RecorderErrorFactory.ThrowIfError(Native.CreateVideo(camera.Handle, out _handle),
65                 "Failed to create Video recorder.");
66
67             Feature = new RecorderFeatures(this);
68             Setting = new RecorderSettings(this);
69
70             RegisterCallbacks();
71
72             SetState(RecorderState.Created);
73         }
74
75         /// <summary>
76         /// Recorder destructor.
77         /// </summary>
78         ~Recorder()
79         {
80             Dispose (false);
81         }
82
83         internal IntPtr GetHandle()
84         {
85             ValidateNotDisposed();
86             return _handle;
87         }
88
89 #region Dispose support
90         /// <summary>
91         /// Release any unmanaged resources used by this object.
92         /// </summary>
93         public void Dispose()
94         {
95             Dispose(true);
96             GC.SuppressFinalize(this);
97         }
98
99         protected virtual void Dispose(bool disposing)
100         {
101             if (!_disposed)
102             {
103                 if (disposing)
104                 {
105                     // to be used if there are any other disposable objects
106                 }
107                 if (_handle != IntPtr.Zero)
108                 {
109                     Native.Destroy(_handle);
110                     _handle = IntPtr.Zero;
111                 }
112                 _disposed = true;
113             }
114         }
115
116         internal void ValidateNotDisposed()
117         {
118             if (_disposed)
119             {
120                 throw new ObjectDisposedException(nameof(Recorder));
121             }
122         }
123 #endregion Dispose support
124
125 #region Check recorder state
126         internal void ValidateState(params RecorderState[] required)
127         {
128             ValidateNotDisposed();
129
130             Debug.Assert(required.Length > 0);
131
132             var curState = _state;
133             if (!required.Contains(curState))
134             {
135                 throw new InvalidOperationException($"The recorder is not in a valid state. " +
136                     $"Current State : { curState }, Valid State : { string.Join(", ", required) }.");
137             }
138         }
139
140         internal void SetState(RecorderState state)
141         {
142             _state = state;
143         }
144 #endregion Check recorder state
145
146 #region EventHandlers
147         /// <summary>
148         /// Event that occurs when an error occurs during recorder operation.
149         /// </summary>
150         public event EventHandler<RecordingErrorOccurredEventArgs> ErrorOccurred;
151         private Native.RecorderErrorCallback _errorOccuredCallback;
152
153         /// <summary>
154         /// Event that occurs when recorder is interrupted.
155         /// </summary>
156         public event EventHandler<RecorderInterruptedEventArgs> Interrupted;
157         private Native.InterruptedCallback _interruptedCallback;
158
159         /// <summary>
160         /// This event occurs when recorder state is changed.
161         /// </summary>
162         public event EventHandler<RecorderStateChangedEventArgs> StateChanged;
163         private Native.StatechangedCallback _stateChangedCallback;
164
165         /// <summary>
166         /// Event that occurs when recording information changes.
167         /// </summary>
168         public event EventHandler<RecordingProgressEventArgs> RecordingProgress;
169         private Native.RecordingProgressCallback _recordingProgressCallback;
170
171         /// <summary>
172         /// Event that occurs when audio stream data is being delivered.
173         /// </summary>
174         public event EventHandler<AudioStreamDeliveredEventArgs> AudioStreamDelivered;
175         private Native.AudioStreamCallback _audioStreamCallback;
176
177         /// <summary>
178         /// Event that occurs when recording limit is reached.
179         /// </summary>
180         public event EventHandler<RecordingLimitReachedEventArgs> RecordingLimitReached;
181         private Native.RecordingLimitReachedCallback _recordingLimitReachedCallback;
182
183         /// <summary>
184         /// Event that occurs when muxed stream data is being delivered.
185         /// </summary>
186         public event EventHandler<MuxedStreamDeliveredEventArgs> MuxedStreamDelivered;
187         private Native.MuxedStreamCallback _muxedStreamCallback;
188 #endregion EventHandlers
189
190 #region Properties
191         /// <summary>
192         /// Gets the various recorder features.
193         /// </summary>
194         public RecorderFeatures Feature { get; }
195
196         /// <summary>
197         /// Get/Set the various recorder settings.
198         /// </summary>
199         public RecorderSettings Setting { get; }
200
201         /// <summary>
202         /// The current state of the recorder.
203         /// </summary>
204         /// <value>A <see cref="RecorderState"/> that specifies the state of recorder.</value>
205         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
206         public RecorderState State
207         {
208             get
209             {
210                 ValidateNotDisposed();
211
212                 RecorderState val = 0;
213
214                 RecorderErrorFactory.ThrowIfError(Native.GetState(_handle, out val),
215                     "Failed to get recorder state.");
216
217                 return val;
218             }
219         }
220 #endregion Properties
221
222 #region Methods
223         /// <summary>
224         /// Prepare the media recorder for recording.
225         /// The recorder must be in the <see cref="RecorderState.Created"/> state.
226         /// After this method is finished without any exception,
227         /// The state of recorder will be changed to <see cref="RecorderState.Ready"/> state.
228         /// </summary>
229         /// <remarks>
230         /// Before calling the function, it is required to set AudioEncoder,
231         /// videoencoder and fileformat properties of recorder.
232         /// </remarks>
233         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
234         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
235         public void Prepare()
236         {
237             ValidateState(RecorderState.Created);
238
239             RecorderErrorFactory.ThrowIfError(Native.Prepare(_handle),
240                 "Failed to prepare media recorder for recording");
241
242             SetState(RecorderState.Ready);
243         }
244
245         /// <summary>
246         /// Resets the media recorder.
247         /// The recorder must be in the <see cref="RecorderState.Ready"/> state.
248         /// After this method is finished without any exception,
249         /// The state of recorder will be changed to <see cref="RecorderState.Created"/> state.
250         /// </summary>
251         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
252         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
253         public void Unprepare()
254         {
255             ValidateState(RecorderState.Ready);
256
257             RecorderErrorFactory.ThrowIfError(Native.Unprepare(_handle),
258                 "Failed to reset the media recorder");
259
260             SetState(RecorderState.Created);
261         }
262
263         /// <summary>
264         /// Starts the recording.
265         /// The recorder must be in the <see cref="RecorderState.Ready"/> state.
266         /// After this method is finished without any exception,
267         /// The state of recorder will be changed to <see cref="RecorderState.Recording"/> state.
268         /// </summary>
269         /// <remarks>
270         /// If file path has been set to an existing file, this file is removed automatically and updated by new one.
271         /// In the video recorder, some preview format does not support record mode. It will return InvalidOperation error.
272         ///     You should use default preview format or CameraPixelFormatNv12 in the record mode.
273         ///     The filename should be set before this function is invoked.
274         /// </remarks>
275         /// <privilege>
276         /// http://tizen.org/privilege/recorder
277         /// </privilege>
278         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
279         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
280         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
281         public void Start()
282         {
283             ValidateState(RecorderState.Ready, RecorderState.Paused);
284
285             RecorderErrorFactory.ThrowIfError(Native.Start(_handle),
286                 "Failed to start the media recorder");
287
288             SetState(RecorderState.Recording);
289         }
290
291         /// <summary>
292         /// Pause the recording.
293         /// The recorder must be in the <see cref="RecorderState.Recording"/> state.
294         /// After this method is finished without any exception,
295         /// The state of recorder will be changed to <see cref="RecorderState.Paused"/> state.
296         /// </summary>
297         /// <remarks>
298         /// Recording can be resumed with Start().
299         /// </remarks>
300         /// <privilege>
301         /// http://tizen.org/privilege/recorder
302         /// </privilege>
303         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
304         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
305         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
306         public void Pause()
307         {
308             ValidateState(RecorderState.Recording);
309
310             RecorderErrorFactory.ThrowIfError(Native.Pause(_handle),
311                 "Failed to pause the media recorder");
312
313             SetState(RecorderState.Paused);
314         }
315
316         /// <summary>
317         /// Stops recording and saves the result.
318         /// The recorder must be in the <see cref="RecorderState.Recording"/> or <see cref="RecorderState.Paused"/> state.
319         /// After this method is finished without any exception,
320         /// The state of recorder will be changed to <see cref="RecorderState.Ready"/> state.
321         /// </summary>
322         /// <privilege>
323         /// http://tizen.org/privilege/recorder
324         /// </privilege>
325         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
326         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
327         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
328         public void Commit()
329         {
330             ValidateState(RecorderState.Recording, RecorderState.Paused);
331
332             RecorderErrorFactory.ThrowIfError(Native.Commit(_handle),
333                 "Failed to save the recorded content");
334
335             SetState(RecorderState.Ready);
336         }
337
338         /// <summary>
339         /// Cancels the recording.
340         /// The recording data is discarded and not written in the recording file.
341         /// The recorder must be in the <see cref="RecorderState.Recording"/> or <see cref="RecorderState.Paused"/> state.
342         /// After this method is finished without any exception,
343         /// The state of recorder will be changed to <see cref="RecorderState.Ready"/> state.
344         /// </summary>
345         /// <privilege>
346         /// http://tizen.org/privilege/recorder
347         /// </privilege>
348         /// <exception cref="InvalidOperationException">In case of any invalid operations.</exception>
349         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
350         /// <exception cref="UnauthorizedAccessException">In case of access to the resources cannot be granted.</exception>
351         public void Cancel()
352         {
353             ValidateState(RecorderState.Recording, RecorderState.Paused);
354
355             RecorderErrorFactory.ThrowIfError(Native.Cancel(_handle),
356                 "Failed to cancel the recording");
357
358             SetState(RecorderState.Ready);
359         }
360
361         /// <summary>
362         /// Sets the audio stream policy.
363         /// </summary>
364         /// <param name="policy">Policy.</param>
365         /// <exception cref="ObjectDisposedException">The camera already has been disposed.</exception>
366         public void SetAudioStreamPolicy(AudioStreamPolicy policy)
367         {
368             ValidateNotDisposed();
369
370             RecorderErrorFactory.ThrowIfError(Native.SetAudioStreamPolicy(_handle, policy.Handle),
371                 "Failed to set audio stream policy");
372         }
373 #endregion Methods
374
375 #region Callback registrations
376         private void RegisterCallbacks()
377         {
378             RegisterErrorCallback();
379             RegisterInterruptedCallback();
380             RegisterStateChangedCallback();
381             RegisterRecordingProgressCallback();
382             RegisterAudioStreamDeliveredCallback();
383             RegisterRecordingLimitReachedEvent();
384             RegisterMuxedStreamEvent();
385         }
386
387         private void RegisterErrorCallback()
388         {
389             _errorOccuredCallback = (RecorderErrorCode error, RecorderState current, IntPtr userData) =>
390             {
391                 ErrorOccurred?.Invoke(this, new RecordingErrorOccurredEventArgs(error, current));
392             };
393             RecorderErrorFactory.ThrowIfError(Native.SetErrorCallback(_handle, _errorOccuredCallback, IntPtr.Zero),
394                 "Setting Error callback failed");
395         }
396
397         private void RegisterInterruptedCallback()
398         {
399             _interruptedCallback = (RecorderPolicy policy, RecorderState previous, RecorderState current, IntPtr userData) =>
400             {
401                 Interrupted?.Invoke(this, new RecorderInterruptedEventArgs(policy, previous, current));
402             };
403             RecorderErrorFactory.ThrowIfError(Native.SetInterruptedCallback(_handle, _interruptedCallback, IntPtr.Zero),
404                 "Setting Interrupted callback failed");
405         }
406
407         private void RegisterStateChangedCallback()
408         {
409             _stateChangedCallback = (RecorderState previous, RecorderState current, bool byPolicy, IntPtr userData) =>
410             {
411                 SetState(current);
412                 Log.Info(RecorderLog.Tag, "Recorder state changed " + previous.ToString() + " -> " + current.ToString());
413                 StateChanged?.Invoke(this, new RecorderStateChangedEventArgs(previous, current, byPolicy));
414             };
415             RecorderErrorFactory.ThrowIfError(Native.SetStateChangedCallback(_handle, _stateChangedCallback, IntPtr.Zero),
416                 "Setting state changed callback failed");
417         }
418
419         private void RegisterRecordingProgressCallback()
420         {
421             _recordingProgressCallback = (ulong elapsedTime, ulong fileSize, IntPtr userData) =>
422             {
423                 RecordingProgress?.Invoke(this, new RecordingProgressEventArgs(elapsedTime, fileSize));
424             };
425             RecorderErrorFactory.ThrowIfError(Native.SetRecordingProgressCallback(_handle, _recordingProgressCallback, IntPtr.Zero),
426                 "Setting status changed callback failed");
427         }
428
429         private void RegisterAudioStreamDeliveredCallback()
430         {
431             _audioStreamCallback = (IntPtr stream, int streamSize, AudioSampleType type, int channel, uint recordingTime, IntPtr userData) =>
432             {
433                 AudioStreamDelivered?.Invoke(this, new AudioStreamDeliveredEventArgs(stream, streamSize, type, channel, recordingTime));
434             };
435             RecorderErrorFactory.ThrowIfError(Native.SetAudioStreamCallback(_handle, _audioStreamCallback, IntPtr.Zero),
436                 "Setting audiostream callback failed");
437         }
438
439         private void RegisterRecordingLimitReachedEvent()
440         {
441             _recordingLimitReachedCallback = (RecordingLimitType type, IntPtr userData) =>
442             {
443                 RecordingLimitReached?.Invoke(this, new RecordingLimitReachedEventArgs(type));
444             };
445             RecorderErrorFactory.ThrowIfError(Native.SetLimitReachedCallback(_handle, _recordingLimitReachedCallback, IntPtr.Zero),
446                 "Setting limit reached callback failed");
447         }
448
449         private void RegisterMuxedStreamEvent()
450         {
451             _muxedStreamCallback = (IntPtr stream, int streamSize, ulong offset, IntPtr userData) =>
452             {
453                 MuxedStreamDelivered?.Invoke(this, new MuxedStreamDeliveredEventArgs(stream, streamSize, offset));
454             };
455             RecorderErrorFactory.ThrowIfError(Native.SetMuxedStreamCallback(_handle, _muxedStreamCallback, IntPtr.Zero),
456                 "Setting muxed stream callback failed");
457         }
458 #endregion Callback registrations
459     }
460 }