MediaRecorder stopRecorder() returns empty Blob after first use
[WebKit-https.git] / Source / WebCore / Modules / mediarecorder / MediaRecorder.cpp
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 "MediaRecorderProvider.h"
38 #include "Page.h"
39 #include "SharedBuffer.h"
40 #include "WindowEventLoop.h"
41 #include <wtf/IsoMallocInlines.h>
42
43 namespace WebCore {
44
45 WTF_MAKE_ISO_ALLOCATED_IMPL(MediaRecorder);
46
47 MediaRecorder::CreatorFunction MediaRecorder::m_customCreator = nullptr;
48
49 ExceptionOr<Ref<MediaRecorder>> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options)
50 {
51     auto privateInstance = MediaRecorder::createMediaRecorderPrivate(document, stream->privateStream());
52     if (!privateInstance)
53         return Exception { NotSupportedError, "The MediaRecorder is unsupported on this platform"_s };
54     auto recorder = adoptRef(*new MediaRecorder(document, WTFMove(stream), WTFMove(options)));
55     recorder->suspendIfNeeded();
56     return recorder;
57 }
58
59 void MediaRecorder::setCustomPrivateRecorderCreator(CreatorFunction creator)
60 {
61     m_customCreator = creator;
62 }
63
64 std::unique_ptr<MediaRecorderPrivate> MediaRecorder::createMediaRecorderPrivate(Document& document, MediaStreamPrivate& stream)
65 {
66     if (m_customCreator)
67         return m_customCreator(stream);
68
69 #if PLATFORM(COCOA)
70     auto* page = document.page();
71     if (!page)
72         return nullptr;
73
74     return page->mediaRecorderProvider().createMediaRecorderPrivate(stream);
75 #else
76     UNUSED_PARAM(document);
77     UNUSED_PARAM(stream);
78     return nullptr;
79 #endif
80 }
81
82 MediaRecorder::MediaRecorder(Document& document, Ref<MediaStream>&& stream, Options&& option)
83     : ActiveDOMObject(document)
84     , m_options(WTFMove(option))
85     , m_stream(WTFMove(stream))
86 {
87     m_tracks = WTF::map(m_stream->getTracks(), [] (auto&& track) -> Ref<MediaStreamTrackPrivate> {
88         return track->privateTrack();
89     });
90     m_stream->privateStream().addObserver(*this);
91 }
92
93 MediaRecorder::~MediaRecorder()
94 {
95     m_stream->privateStream().removeObserver(*this);
96     stopRecordingInternal();
97 }
98
99 Document* MediaRecorder::document() const
100 {
101     return downcast<Document>(scriptExecutionContext());
102 }
103
104 void MediaRecorder::stop()
105 {
106     m_isActive = false;
107     stopRecordingInternal();
108 }
109
110 void MediaRecorder::suspend(ReasonForSuspension reason)
111 {
112     if (reason != ReasonForSuspension::BackForwardCache)
113         return;
114
115     if (!m_isActive || state() == RecordingState::Inactive)
116         return;
117
118     stopRecordingInternal();
119
120     queueTaskToDispatchEvent(*this, TaskSource::Networking, MediaRecorderErrorEvent::create(eventNames().errorEvent, Exception { UnknownError, "MediaStream recording was interrupted"_s }));
121 }
122
123 const char* MediaRecorder::activeDOMObjectName() const
124 {
125     return "MediaRecorder";
126 }
127
128 ExceptionOr<void> MediaRecorder::startRecording(Optional<int> timeslice)
129 {
130     UNUSED_PARAM(timeslice);
131     if (!m_isActive)
132         return Exception { InvalidStateError, "The MediaRecorder is not active"_s };
133
134     if (state() != RecordingState::Inactive)
135         return Exception { InvalidStateError, "The MediaRecorder's state must be inactive in order to start recording"_s };
136
137     ASSERT(!m_private);
138     m_private = createMediaRecorderPrivate(*document(), m_stream->privateStream());
139
140     if (!m_private)
141         return Exception { NotSupportedError, "The MediaRecorder is unsupported on this platform"_s };
142
143     m_private->startRecording([this, pendingActivity = makePendingActivity(*this)](auto&& exception) mutable {
144         if (!m_isActive || !exception)
145             return;
146
147         stopRecordingInternal();
148         dispatchError(WTFMove(*exception));
149     });
150
151     for (auto& track : m_tracks)
152         track->addObserver(*this);
153
154     m_state = RecordingState::Recording;
155     return { };
156 }
157
158 ExceptionOr<void> MediaRecorder::stopRecording()
159 {
160     if (state() == RecordingState::Inactive)
161         return Exception { InvalidStateError, "The MediaRecorder's state cannot be inactive"_s };
162
163     stopRecordingInternal();
164     auto& privateRecorder = *m_private;
165     privateRecorder.fetchData([this, pendingActivity = makePendingActivity(*this), privateRecorder = WTFMove(m_private)](auto&& buffer, auto& mimeType) {
166         queueTaskKeepingObjectAlive(*this, TaskSource::Networking, [this, buffer = WTFMove(buffer), mimeType]() mutable {
167             if (!m_isActive)
168                 return;
169             dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, buffer ? Blob::create(buffer.releaseNonNull(), mimeType) : Blob::create()));
170
171             if (!m_isActive)
172                 return;
173             dispatchEvent(Event::create(eventNames().stopEvent, Event::CanBubble::No, Event::IsCancelable::No));
174         });
175     });
176     return { };
177 }
178
179 ExceptionOr<void> MediaRecorder::requestData()
180 {
181     if (state() == RecordingState::Inactive)
182         return Exception { InvalidStateError, "The MediaRecorder's state cannot be inactive"_s };
183
184     m_private->fetchData([this, pendingActivity = makePendingActivity(*this)](auto&& buffer, auto& mimeType) {
185         queueTaskKeepingObjectAlive(*this, TaskSource::Networking, [this, buffer = WTFMove(buffer), mimeType]() mutable {
186             if (!m_isActive)
187                 return;
188
189             dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, buffer ? Blob::create(buffer.releaseNonNull(), mimeType) : Blob::create()));
190         });
191     });
192     return { };
193 }
194
195 void MediaRecorder::stopRecordingInternal()
196 {
197     if (state() != RecordingState::Recording)
198         return;
199
200     for (auto& track : m_tracks)
201         track->removeObserver(*this);
202
203     m_state = RecordingState::Inactive;
204     m_private->stopRecording();
205 }
206
207 void MediaRecorder::handleTrackChange()
208 {
209     queueTaskKeepingObjectAlive(*this, TaskSource::Networking, [this] {
210         if (!m_isActive || state() == RecordingState::Inactive)
211             return;
212         stopRecordingInternal();
213         dispatchError(Exception { UnknownError, "Track cannot be added to or removed from the MediaStream while recording is happening"_s });
214     });
215 }
216
217 void MediaRecorder::dispatchError(Exception&& exception)
218 {
219     if (!m_isActive)
220         return;
221     dispatchEvent(MediaRecorderErrorEvent::create(eventNames().errorEvent, WTFMove(exception)));
222 }
223
224 void MediaRecorder::trackEnded(MediaStreamTrackPrivate&)
225 {
226     auto position = m_tracks.findMatching([](auto& track) {
227         return !track->ended();
228     });
229     if (position != notFound)
230         return;
231
232     stopRecording();
233 }
234
235 bool MediaRecorder::virtualHasPendingActivity() const
236 {
237     return m_state != RecordingState::Inactive;
238 }
239
240 } // namespace WebCore
241
242 #endif // ENABLE(MEDIA_STREAM)