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 | |
44 | namespace WebCore { |
45 | |
46 | WTF_MAKE_ISO_ALLOCATED_IMPL(MediaRecorder); |
47 | |
48 | creatorFunction MediaRecorder::m_customCreator = nullptr; |
49 | |
50 | ExceptionOr<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 | |
60 | void MediaRecorder::setCustomPrivateRecorderCreator(creatorFunction creator) |
61 | { |
62 | m_customCreator = creator; |
63 | } |
64 | |
65 | std::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 | |
78 | MediaRecorder::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 | |
90 | MediaRecorder::~MediaRecorder() |
91 | { |
92 | m_stream->removeObserver(this); |
93 | stopRecordingInternal(); |
94 | } |
95 | |
96 | void MediaRecorder::stop() |
97 | { |
98 | m_isActive = false; |
99 | stopRecordingInternal(); |
100 | } |
101 | |
102 | const char* MediaRecorder::activeDOMObjectName() const |
103 | { |
104 | return "MediaRecorder" ; |
105 | } |
106 | |
107 | bool MediaRecorder::canSuspendForDocumentSuspension() const |
108 | { |
109 | return false; // FIXME: We should do better here as this prevents entering PageCache. |
110 | } |
111 | |
112 | ExceptionOr<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 | |
125 | ExceptionOr<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 | |
144 | void 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 | |
156 | Ref<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 | |
164 | void 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 | |
175 | void 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 | |
186 | void MediaRecorder::sampleBufferUpdated(MediaStreamTrackPrivate& track, MediaSample& mediaSample) |
187 | { |
188 | m_private->sampleBufferUpdated(track, mediaSample); |
189 | } |
190 | |
191 | void 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 | |
196 | void 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 | |