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