1/*
2 * Copyright (C) 2018 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25
26#include "config.h"
27#include "MediaRecorder.h"
28
29#if ENABLE(MEDIA_STREAM)
30
31#include "Blob.h"
32#include "BlobEvent.h"
33#include "Document.h"
34#include "EventNames.h"
35#include "MediaRecorderErrorEvent.h"
36#include "MediaRecorderPrivate.h"
37#include "SharedBuffer.h"
38#include <wtf/IsoMallocInlines.h>
39
40#if PLATFORM(COCOA)
41#include "MediaRecorderPrivateAVFImpl.h"
42#endif
43
44namespace WebCore {
45
46WTF_MAKE_ISO_ALLOCATED_IMPL(MediaRecorder);
47
48creatorFunction MediaRecorder::m_customCreator = nullptr;
49
50ExceptionOr<Ref<MediaRecorder>> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options)
51{
52 auto privateInstance = MediaRecorder::getPrivateImpl(stream->privateStream());
53 if (!privateInstance)
54 return Exception { NotSupportedError, "The MediaRecorder is unsupported on this platform"_s };
55 auto recorder = adoptRef(*new MediaRecorder(document, WTFMove(stream), WTFMove(privateInstance), WTFMove(options)));
56 recorder->suspendIfNeeded();
57 return recorder;
58}
59
60void MediaRecorder::setCustomPrivateRecorderCreator(creatorFunction creator)
61{
62 m_customCreator = creator;
63}
64
65std::unique_ptr<MediaRecorderPrivate> MediaRecorder::getPrivateImpl(const MediaStreamPrivate& stream)
66{
67 if (m_customCreator)
68 return m_customCreator();
69
70#if PLATFORM(COCOA)
71 return MediaRecorderPrivateAVFImpl::create(stream);
72#else
73 UNUSED_PARAM(stream);
74 return nullptr;
75#endif
76}
77
78MediaRecorder::MediaRecorder(Document& document, Ref<MediaStream>&& stream, std::unique_ptr<MediaRecorderPrivate>&& privateImpl, Options&& option)
79 : ActiveDOMObject(document)
80 , m_options(WTFMove(option))
81 , m_stream(WTFMove(stream))
82 , m_private(WTFMove(privateImpl))
83{
84 m_tracks = WTF::map(m_stream->getTracks(), [] (auto&& track) -> Ref<MediaStreamTrackPrivate> {
85 return track->privateTrack();
86 });
87 m_stream->addObserver(this);
88}
89
90MediaRecorder::~MediaRecorder()
91{
92 m_stream->removeObserver(this);
93 stopRecordingInternal();
94}
95
96void MediaRecorder::stop()
97{
98 m_isActive = false;
99 stopRecordingInternal();
100}
101
102const char* MediaRecorder::activeDOMObjectName() const
103{
104 return "MediaRecorder";
105}
106
107bool MediaRecorder::canSuspendForDocumentSuspension() const
108{
109 return false; // FIXME: We should do better here as this prevents entering PageCache.
110}
111
112ExceptionOr<void> MediaRecorder::startRecording(Optional<int> timeslice)
113{
114 UNUSED_PARAM(timeslice);
115 if (state() != RecordingState::Inactive)
116 return Exception { InvalidStateError, "The MediaRecorder's state must be inactive in order to start recording"_s };
117
118 for (auto& track : m_tracks)
119 track->addObserver(*this);
120
121 m_state = RecordingState::Recording;
122 return { };
123}
124
125ExceptionOr<void> MediaRecorder::stopRecording()
126{
127 if (state() == RecordingState::Inactive)
128 return Exception { InvalidStateError, "The MediaRecorder's state cannot be inactive"_s };
129
130 scheduleDeferredTask([this] {
131 if (!m_isActive || state() == RecordingState::Inactive)
132 return;
133
134 stopRecordingInternal();
135 ASSERT(m_state == RecordingState::Inactive);
136 dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, createRecordingDataBlob()));
137 if (!m_isActive)
138 return;
139 dispatchEvent(Event::create(eventNames().stopEvent, Event::CanBubble::No, Event::IsCancelable::No));
140 });
141 return { };
142}
143
144void MediaRecorder::stopRecordingInternal()
145{
146 if (state() != RecordingState::Recording)
147 return;
148
149 for (auto& track : m_tracks)
150 track->removeObserver(*this);
151
152 m_state = RecordingState::Inactive;
153 m_private->stopRecording();
154}
155
156Ref<Blob> MediaRecorder::createRecordingDataBlob()
157{
158 auto data = m_private->fetchData();
159 if (!data)
160 return Blob::create();
161 return Blob::create(*data, m_private->mimeType());
162}
163
164void MediaRecorder::didAddOrRemoveTrack()
165{
166 scheduleDeferredTask([this] {
167 if (!m_isActive || state() == RecordingState::Inactive)
168 return;
169 stopRecordingInternal();
170 auto event = MediaRecorderErrorEvent::create(eventNames().errorEvent, Exception { UnknownError, "Track cannot be added to or removed from the MediaStream while recording is happening"_s });
171 dispatchEvent(WTFMove(event));
172 });
173}
174
175void MediaRecorder::trackEnded(MediaStreamTrackPrivate&)
176{
177 auto position = m_tracks.findMatching([](auto& track) {
178 return !track->ended();
179 });
180 if (position != notFound)
181 return;
182
183 stopRecording();
184}
185
186void MediaRecorder::sampleBufferUpdated(MediaStreamTrackPrivate& track, MediaSample& mediaSample)
187{
188 m_private->sampleBufferUpdated(track, mediaSample);
189}
190
191void MediaRecorder::audioSamplesAvailable(MediaStreamTrackPrivate& track, const MediaTime& mediaTime, const PlatformAudioData& audioData, const AudioStreamDescription& description, size_t sampleCount)
192{
193 m_private->audioSamplesAvailable(track, mediaTime, audioData, description, sampleCount);
194}
195
196void MediaRecorder::scheduleDeferredTask(Function<void()>&& function)
197{
198 ASSERT(function);
199 auto* scriptExecutionContext = this->scriptExecutionContext();
200 if (!scriptExecutionContext)
201 return;
202
203 scriptExecutionContext->postTask([protectedThis = makeRef(*this), function = WTFMove(function)] (auto&) {
204 function();
205 });
206}
207
208} // namespace WebCore
209
210#endif // ENABLE(MEDIA_STREAM)
211