WebKit Bugzilla
Attachment 356237 Details for
Bug 192069
: Implement non-timeslice mode encoding for MediaRecorder
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-192069-20181130142602.patch (text/plain), 62.07 KB, created by
Wendy
on 2018-11-30 14:26:03 PST
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Wendy
Created:
2018-11-30 14:26:03 PST
Size:
62.07 KB
patch
obsolete
>Subversion Revision: 237702 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 57c1ddc50e6356bd45c582774edfd4f540de5875..4050eb39c137e95c298944c8c7c50fc8d684039d 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,63 @@ >+2018-11-30 YUHAN WU <yuhan_wu@apple.com> >+ >+ Implement non-timeslice mode encoding for MediaRecorder >+ https://bugs.webkit.org/show_bug.cgi?id=192069 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Implement the encoding for non-timeslice mode of MediaRecorder. >+ It only supports to record MP4 file through H264 and AAC encoding, we will need to support more MIME types and encoding methods. >+ Add a API in internals to allow testings to turn on the mock source. >+ >+ Tests: http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html >+ http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html >+ http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html >+ >+ * Modules/mediarecorder/MediaRecorder.cpp: >+ (WebCore::MediaRecorder::create): >+ (WebCore::MediaRecorder::setCustomPrivateRecorderCreator): >+ (WebCore::MediaRecorder::getPrivateImpl): >+ (WebCore::MediaRecorder::MediaRecorder): >+ (WebCore::MediaRecorder::stopRecording): >+ (WebCore::MediaRecorder::stopRecordingInternal): >+ (WebCore::MediaRecorder::createBlobByRecordingData): >+ (WebCore::MediaRecorder::scheduleDeferredTask): >+ * Modules/mediarecorder/MediaRecorder.h: >+ * Modules/mediarecorder/MediaRecorder.idl: >+ * SourcesCocoa.txt: >+ * WebCore.xcodeproj/project.pbxproj: >+ * platform/mediarecorder/MediaRecorderPrivate.h: >+ (WebCore::MediaRecorderPrivate::stopRecording): >+ * platform/mediarecorder/MediaRecorderPrivateAVImpl.cpp: Added. >+ (WebCore::MediaRecorderPrivateAVImpl::create): >+ (WebCore::MediaRecorderPrivateAVImpl::MediaRecorderPrivateAVImpl): >+ (WebCore::MediaRecorderPrivateAVImpl::sampleBufferUpdated): >+ (WebCore::MediaRecorderPrivateAVImpl::audioSamplesAvailable): >+ (WebCore::MediaRecorderPrivateAVImpl::stopRecording): >+ (WebCore::MediaRecorderPrivateAVImpl::fetchData): >+ (WebCore::MediaRecorderPrivateAVImpl::mimeType): >+ * platform/mediarecorder/MediaRecorderPrivateAVImpl.h: Added. >+ * platform/mediarecorder/MediaRecorderPrivateMock.cpp: >+ (WebCore::MediaRecorderPrivateMock::fetchData): >+ (WebCore::MediaRecorderPrivateMock::mimeType): >+ * platform/mediarecorder/MediaRecorderPrivateMock.h: >+ * platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h: Added. >+ (WebCore::MediaRecorderPrivateWriter::isWriterReady): >+ * platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm: Added. >+ (WebCore::MediaRecorderPrivateWriter::setupWriter): >+ (WebCore::MediaRecorderPrivateWriter::setVideoInput): >+ (WebCore::MediaRecorderPrivateWriter::setAudioInput): >+ (WebCore::copySampleBufferWithCurrentTimeStamp): >+ (WebCore::MediaRecorderPrivateWriter::appendVideoSampleBuffer): >+ (WebCore::MediaRecorderPrivateWriter::appendAudioSampleBuffer): >+ (WebCore::MediaRecorderPrivateWriter::stopRecording): >+ (WebCore::MediaRecorderPrivateWriter::fetchData): >+ * testing/Internals.cpp: >+ (WebCore::createRecorderMockSource): >+ (WebCore::Internals::setCustomPrivateRecorderCreator): >+ * testing/Internals.h: >+ * testing/Internals.idl: >+ > 2018-11-01 Chris Dumez <cdumez@apple.com> > > Location object sans browsing context >diff --git a/Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp b/Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp >index 8f6bcf746b6a476b76fbc4abddc6559b009cb9c5..1137608279dc7ec2e2185d49d585e495109c0b85 100644 >--- a/Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp >+++ b/Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp >@@ -33,22 +33,46 @@ > #include "Document.h" > #include "EventNames.h" > #include "MediaRecorderErrorEvent.h" >+#if PLATFORM(COCOA) >+#include "MediaRecorderPrivateAVImpl.h" >+#endif > #include "MediaRecorderPrivateMock.h" >+#include "SharedBuffer.h" > > namespace WebCore { > >-Ref<MediaRecorder> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options) >+creatorFunction MediaRecorder::m_customCreator = nullptr; >+ >+ExceptionOr<Ref<MediaRecorder>> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options) > { > auto recorder = adoptRef(*new MediaRecorder(document, WTFMove(stream), WTFMove(options))); > recorder->suspendIfNeeded(); >+ if (!recorder->m_private) >+ return Exception { NotSupportedError, "The MediaRecorder of audio and video source is only supported on iOS and macOS"_s }; > return recorder; > } > >+void MediaRecorder::setCustomPrivateRecorderCreator(creatorFunction creator) >+{ >+ m_customCreator = creator; >+} >+ >+std::unique_ptr<MediaRecorderPrivate> MediaRecorder::getPrivateImpl(const MediaStreamPrivate& stream) >+{ >+ if (m_customCreator) >+ return m_customCreator(); >+ >+#if PLATFORM(COCOA) >+ return MediaRecorderPrivateAVImpl::create(stream); >+#endif >+ return nullptr; >+} >+ > MediaRecorder::MediaRecorder(Document& document, Ref<MediaStream>&& stream, Options&& option) > : ActiveDOMObject(&document) > , m_options(WTFMove(option)) > , m_stream(WTFMove(stream)) >- , m_private(makeUniqueRef<MediaRecorderPrivateMock>()) // FIXME: we will need to decide which MediaRecorderPrivate instance to create based on the mock enabled feature flag >+ , m_private(MediaRecorder::getPrivateImpl(m_stream->privateStream())) > { > m_tracks = WTF::map(m_stream->getTracks(), [] (auto&& track) -> Ref<MediaStreamTrackPrivate> { > return track->privateTrack(); >@@ -95,14 +119,14 @@ ExceptionOr<void> MediaRecorder::stopRecording() > { > if (state() == RecordingState::Inactive) > return Exception { InvalidStateError, "The MediaRecorder's state cannot be inactive"_s }; >- >+ > scheduleDeferredTask([this] { > if (!m_isActive || state() == RecordingState::Inactive) > return; > > stopRecordingInternal(); > ASSERT(m_state == RecordingState::Inactive); >- dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, m_private->fetchData())); >+ dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, createBlobByRecordingData())); > if (!m_isActive) > return; > dispatchEvent(Event::create(eventNames().stopEvent, Event::CanBubble::No, Event::IsCancelable::No)); >@@ -119,6 +143,15 @@ void MediaRecorder::stopRecordingInternal() > track->removeObserver(*this); > > m_state = RecordingState::Inactive; >+ m_private->stopRecording(); >+} >+ >+Ref<Blob> MediaRecorder::createBlobByRecordingData() >+{ >+ auto data = m_private->fetchData(); >+ if (!data) >+ return Blob::create(); >+ return Blob::create(*data, m_private->mimeType()); > } > > void MediaRecorder::didAddOrRemoveTrack() >@@ -164,6 +197,7 @@ void MediaRecorder::scheduleDeferredTask(Function<void()>&& function) > auto* scriptExecutionContext = this->scriptExecutionContext(); > if (!scriptExecutionContext) > return; >+ > scriptExecutionContext->postTask([protectedThis = makeRef(*this), function = WTFMove(function)] (auto&) { > function(); > }); >diff --git a/Source/WebCore/Modules/mediarecorder/MediaRecorder.h b/Source/WebCore/Modules/mediarecorder/MediaRecorder.h >index 7c8aea5772c671fdbc77bae2540393b634270fdf..536f9074cd2700d96e34397b09c7a3acbfc410f8 100644 >--- a/Source/WebCore/Modules/mediarecorder/MediaRecorder.h >+++ b/Source/WebCore/Modules/mediarecorder/MediaRecorder.h >@@ -34,9 +34,12 @@ > > namespace WebCore { > >+class Blob; > class Document; > class MediaRecorderPrivate; > >+typedef std::unique_ptr<MediaRecorderPrivate>(*creatorFunction)(); >+ > class MediaRecorder final > : public ActiveDOMObject > , public RefCounted<MediaRecorder> >@@ -56,7 +59,9 @@ public: > > ~MediaRecorder(); > >- static Ref<MediaRecorder> create(Document&, Ref<MediaStream>&&, Options&& = { }); >+ static ExceptionOr<Ref<MediaRecorder>> create(Document&, Ref<MediaStream>&&, Options&& = { }); >+ >+ WEBCORE_EXPORT static void setCustomPrivateRecorderCreator(creatorFunction); > > RecordingState state() const { return m_state; } > >@@ -69,6 +74,10 @@ public: > private: > MediaRecorder(Document&, Ref<MediaStream>&&, Options&& = { }); > >+ static std::unique_ptr<MediaRecorderPrivate> getPrivateImpl(const MediaStreamPrivate&); >+ >+ Ref<Blob> createBlobByRecordingData(); >+ > // EventTarget > void refEventTarget() final { ref(); } > void derefEventTarget() final { deref(); } >@@ -96,15 +105,17 @@ private: > void scheduleDeferredTask(Function<void()>&&); > void setNewRecordingState(RecordingState, Ref<Event>&&); > >+ static creatorFunction m_customCreator; >+ > Options m_options; > Ref<MediaStream> m_stream; >- UniqueRef<MediaRecorderPrivate> m_private; >+ std::unique_ptr<MediaRecorderPrivate> m_private; > RecordingState m_state { RecordingState::Inactive }; > Vector<Ref<MediaStreamTrackPrivate>> m_tracks; > > bool m_isActive { true }; > }; >- >+ > } // namespace WebCore > > #endif // ENABLE(MEDIA_STREAM) >diff --git a/Source/WebCore/Modules/mediarecorder/MediaRecorder.idl b/Source/WebCore/Modules/mediarecorder/MediaRecorder.idl >index 469738033a0fe53b54d0cc1b5194119cc448a791..8c249b1f3adce89518021c4ca38bb36171bb3ca9 100644 >--- a/Source/WebCore/Modules/mediarecorder/MediaRecorder.idl >+++ b/Source/WebCore/Modules/mediarecorder/MediaRecorder.idl >@@ -29,6 +29,7 @@ enum RecordingState { "inactive", "recording", "paused" }; > Conditional=MEDIA_STREAM, > Constructor(MediaStream stream, optional MediaRecorderOptions options), > ConstructorCallWith=Document, >+ ConstructorMayThrowException, > EnabledAtRuntime=MediaRecorder, > Exposed=Window > ] interface MediaRecorder : EventTarget { >diff --git a/Source/WebCore/SourcesCocoa.txt b/Source/WebCore/SourcesCocoa.txt >index a258d08f4e4fb6b457c6eacea17d611b296100ad..b460ba6271125376d3354be3c40b7339f27f33a5 100644 >--- a/Source/WebCore/SourcesCocoa.txt >+++ b/Source/WebCore/SourcesCocoa.txt >@@ -473,6 +473,9 @@ platform/mac/WebNSAttributedStringExtras.mm > platform/mac/WebPlaybackControlsManager.mm > platform/mac/WidgetMac.mm > >+platform/mediarecorder/MediaRecorderPrivateAVImpl.cpp >+platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm >+ > platform/mediasession/mac/MediaSessionInterruptionProviderMac.mm > > platform/mediastream/ios/AVAudioSessionCaptureDevice.mm >diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >index 11cb0e7c569f50b6baaaab5d719825cd6e10a5b2..6bb2bf5d95e914ebd65ba6fbd46956814ad46ae3 100644 >--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj >+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj >@@ -1361,6 +1361,8 @@ > 4D3B00AB215D69A70076B983 /* MediaRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B00A9215D69A70076B983 /* MediaRecorder.h */; }; > 4D3B00AF215D6A690076B983 /* BlobEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B00AD215D6A690076B983 /* BlobEvent.h */; }; > 4D3B5016217E58B700665DB1 /* MediaRecorderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */; }; >+ 4D73F946218BC5FA003A3ED6 /* MediaRecorderPrivateAVImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVImpl.h */; }; >+ 4D73F94E218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */; }; > 4DB7130D216ECB4D0096A4DD /* MediaRecorderErrorEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DB7130C216EC2BD0096A4DD /* MediaRecorderErrorEvent.h */; }; > 4E1959220A39DABA00220FE5 /* MediaFeatureNames.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E1959200A39DABA00220FE5 /* MediaFeatureNames.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 4E19592A0A39DACC00220FE5 /* MediaQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E1959240A39DACC00220FE5 /* MediaQuery.h */; settings = {ATTRIBUTES = (Private, ); }; }; >@@ -7922,6 +7924,10 @@ > 4D3B00A9215D69A70076B983 /* MediaRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaRecorder.h; sourceTree = "<group>"; }; > 4D3B00AD215D6A690076B983 /* BlobEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobEvent.h; sourceTree = "<group>"; }; > 4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivate.h; sourceTree = "<group>"; }; >+ 4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateAVImpl.h; sourceTree = "<group>"; }; >+ 4D73F945218BC5FA003A3ED6 /* MediaRecorderPrivateAVImpl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaRecorderPrivateAVImpl.cpp; sourceTree = "<group>"; }; >+ 4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateWriterCocoa.h; sourceTree = "<group>"; }; >+ 4D73F94D218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = MediaRecorderPrivateWriterCocoa.mm; sourceTree = "<group>"; wrapsLines = 0; }; > 4D7EB3F4217C6AE600D64888 /* BlobEvent.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BlobEvent.cpp; sourceTree = "<group>"; }; > 4D9F6B642182532B0092A9C5 /* MediaRecorderPrivateMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateMock.h; sourceTree = "<group>"; }; > 4D9F6B652182532B0092A9C5 /* MediaRecorderPrivateMock.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaRecorderPrivateMock.cpp; sourceTree = "<group>"; }; >@@ -18058,13 +18064,25 @@ > 4D3B5012217E58A300665DB1 /* mediarecorder */ = { > isa = PBXGroup; > children = ( >+ 4D3C05D421AF480900F2890A /* cocoa */, > 4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */, >+ 4D73F945218BC5FA003A3ED6 /* MediaRecorderPrivateAVImpl.cpp */, >+ 4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVImpl.h */, > 4D9F6B652182532B0092A9C5 /* MediaRecorderPrivateMock.cpp */, > 4D9F6B642182532B0092A9C5 /* MediaRecorderPrivateMock.h */, > ); > path = mediarecorder; > sourceTree = "<group>"; > }; >+ 4D3C05D421AF480900F2890A /* cocoa */ = { >+ isa = PBXGroup; >+ children = ( >+ 4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */, >+ 4D73F94D218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.mm */, >+ ); >+ path = cocoa; >+ sourceTree = "<group>"; >+ }; > 510310421BA8C64C003329C0 /* client */ = { > isa = PBXGroup; > children = ( >@@ -29963,6 +29981,8 @@ > 4D3B00AB215D69A70076B983 /* MediaRecorder.h in Headers */, > 4DB7130D216ECB4D0096A4DD /* MediaRecorderErrorEvent.h in Headers */, > 4D3B5016217E58B700665DB1 /* MediaRecorderPrivate.h in Headers */, >+ 4D73F946218BC5FA003A3ED6 /* MediaRecorderPrivateAVImpl.h in Headers */, >+ 4D73F94E218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h in Headers */, > C90843D01B18E47D00B68564 /* MediaRemoteControls.h in Headers */, > CD8ACA8F1D23971900ECC59E /* MediaRemoteSoftLink.h in Headers */, > CEEFCD7A19DB31F7003876D7 /* MediaResourceLoader.h in Headers */, >diff --git a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h >index 1872952b6b680f6793c41dd0e7fd21c56940e748..e7cd6d574ce11b50d28f92428288a573c891b149 100644 >--- a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h >+++ b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h >@@ -33,18 +33,20 @@ class MediaTime; > namespace WebCore { > > class AudioStreamDescription; >-class Blob; >-class PlatformAudioData; > class MediaSample; > class MediaStreamTrackPrivate; >+class PlatformAudioData; >+class SharedBuffer; > > class MediaRecorderPrivate { > public: > virtual void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) = 0; > virtual void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) = 0; > >- virtual Ref<Blob> fetchData() = 0; >+ virtual RefPtr<SharedBuffer> fetchData() = 0; >+ virtual const String& mimeType() = 0; > virtual ~MediaRecorderPrivate() = default; >+ virtual void stopRecording() { } > }; > > } // namespace WebCore >diff --git a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVImpl.cpp b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVImpl.cpp >new file mode 100644 >index 0000000000000000000000000000000000000000..bc042e10f943f2392ce78fe53e39c20777f317bd >--- /dev/null >+++ b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVImpl.cpp >@@ -0,0 +1,119 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED >+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE >+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY >+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES >+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; >+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON >+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS >+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+ >+#include "config.h" >+#include "MediaRecorderPrivateAVImpl.h" >+ >+#if ENABLE(MEDIA_STREAM) >+ >+#include "AudioStreamDescription.h" >+#include "MediaSample.h" >+#include "MediaStreamPrivate.h" >+#include "SharedBuffer.h" >+#include "WebAudioBufferList.h" >+ >+namespace WebCore { >+ >+std::unique_ptr<MediaRecorderPrivateAVImpl> MediaRecorderPrivateAVImpl::create(const MediaStreamPrivate& stream) >+{ >+ auto AVImpl = std::unique_ptr<MediaRecorderPrivateAVImpl>(new MediaRecorderPrivateAVImpl(stream)); >+ if (!AVImpl->m_writer.isWriterReady()) >+ return nullptr; >+ return AVImpl; >+} >+ >+MediaRecorderPrivateAVImpl::MediaRecorderPrivateAVImpl(const MediaStreamPrivate& stream) >+{ >+ if (!m_writer.setupWriter()) >+ return; >+ auto tracks = stream.tracks(); >+ bool videoSelected = false; >+ bool audioSelected = false; >+ for (auto& track : tracks) { >+ if (!track->enabled() || track->ended()) >+ continue; >+ switch (track->type()) { >+ case RealtimeMediaSource::Type::Video: { >+ auto& settings = track->settings(); >+ if (videoSelected || !settings.supportsWidth() || !settings.supportsHeight()) >+ break; >+ // FIXME: we will need to implement support for multiple video tracks, currently we only choose the first track as the recorded track. >+ // FIXME: we would better to throw an exception to JavaScript if setVideoInput failed >+ m_writer.setVideoInput(settings.width(), settings.height()); >+ m_recordedVideoTrackID = track->id(); >+ videoSelected = true; >+ break; >+ } >+ case RealtimeMediaSource::Type::Audio: { >+ if (audioSelected) >+ break; >+ // FIXME: we will need to implement support for multiple audio tracks, currently we only choose the first track as the recorded track. >+ // FIXME: we would better to throw an exception to JavaScript if setAudioInput failed >+ m_writer.setAudioInput(); >+ m_recordedAudioTrackID = track->id(); >+ audioSelected = true; >+ break; >+ } >+ case RealtimeMediaSource::Type::None: >+ break; >+ } >+ } >+} >+ >+void MediaRecorderPrivateAVImpl::sampleBufferUpdated(MediaStreamTrackPrivate& track, MediaSample& sampleBuffer) >+{ >+ if (track.id() != m_recordedVideoTrackID) >+ return; >+ m_writer.appendVideoSampleBuffer(sampleBuffer.platformSample().sample.cmSampleBuffer); >+} >+ >+void MediaRecorderPrivateAVImpl::audioSamplesAvailable(MediaStreamTrackPrivate& track, const WTF::MediaTime& mediaTime, const PlatformAudioData& data, const AudioStreamDescription& description, size_t sampleCount) >+{ >+ if (track.id() != m_recordedAudioTrackID) >+ return; >+ ASSERT(is<WebAudioBufferList>(data)); >+ ASSERT(description.platformDescription().type == PlatformDescription::CAAudioStreamBasicType); >+ m_writer.appendAudioSampleBuffer(data, description, mediaTime, sampleCount); >+} >+ >+void MediaRecorderPrivateAVImpl::stopRecording() >+{ >+ m_writer.stopRecording(); >+} >+ >+RefPtr<SharedBuffer> MediaRecorderPrivateAVImpl::fetchData() >+{ >+ return m_writer.fetchData(); >+} >+ >+const String& MediaRecorderPrivateAVImpl::mimeType() >+{ >+ static NeverDestroyed<const String> mp4MimeType(MAKE_STATIC_STRING_IMPL("video/mp4")); >+ return mp4MimeType; // FIXME: we will need to support more MIME types. >+} >+ >+} // namespace WebCore >+ >+#endif // ENABLE(MEDIA_STREAM) >diff --git a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVImpl.h b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVImpl.h >new file mode 100644 >index 0000000000000000000000000000000000000000..db090a8c843524386824639db7cd8ed3d8280d12 >--- /dev/null >+++ b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVImpl.h >@@ -0,0 +1,56 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED >+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE >+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY >+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES >+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; >+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON >+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS >+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#pragma once >+ >+#if ENABLE(MEDIA_STREAM) >+ >+#include "MediaRecorderPrivate.h" >+#include "MediaRecorderPrivateWriterCocoa.h" >+ >+namespace WebCore { >+ >+class MediaStreamPrivate; >+ >+class MediaRecorderPrivateAVImpl final : public MediaRecorderPrivate { >+public: >+ static std::unique_ptr<MediaRecorderPrivateAVImpl> create(const MediaStreamPrivate&); >+ explicit MediaRecorderPrivateAVImpl(const MediaStreamPrivate&); >+ >+private: >+ void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) final; >+ void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final; >+ RefPtr<SharedBuffer> fetchData() final; >+ const String& mimeType() final; >+ void stopRecording(); >+ >+ String m_recordedVideoTrackID; >+ String m_recordedAudioTrackID; >+ >+ MediaRecorderPrivateWriter m_writer; >+}; >+ >+} // namespace WebCore >+ >+#endif // ENABLE(MEDIA_STREAM) >diff --git a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp >index a4a8bcbd860bd3692ce4be76f521c6690420765e..c34a7afcd9f4d79925cb9313810f0292009b2a99 100644 >--- a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp >+++ b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp >@@ -28,8 +28,8 @@ > > #if ENABLE(MEDIA_STREAM) > >-#include "Blob.h" > #include "MediaStreamTrackPrivate.h" >+#include "SharedBuffer.h" > > namespace WebCore { > >@@ -56,13 +56,19 @@ void MediaRecorderPrivateMock::generateMockString(MediaStreamTrackPrivate& track > m_buffer.append("\r\n---------\r\n"); > } > >-Ref<Blob> MediaRecorderPrivateMock::fetchData() >+RefPtr<SharedBuffer> MediaRecorderPrivateMock::fetchData() > { > auto locker = holdLock(m_bufferLock); > Vector<uint8_t> value(m_buffer.length()); > memcpy(value.data(), m_buffer.characters8(), m_buffer.length()); > m_buffer.clear(); >- return Blob::create(WTFMove(value), "text/plain"); >+ return SharedBuffer::create(WTFMove(value)); >+} >+ >+const String& MediaRecorderPrivateMock::mimeType() >+{ >+ static NeverDestroyed<const String> textPlainMimeType(MAKE_STATIC_STRING_IMPL("text/plain")); >+ return textPlainMimeType; > } > > } // namespace WebCore >diff --git a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h >index 4d9adb60392e7a9ebf191ea7180616c9a8f004b1..6ca817315f870b4e83aec761259f57b09bab97a6 100644 >--- a/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h >+++ b/Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h >@@ -32,14 +32,14 @@ > > namespace WebCore { > >-class Blob; > class MediaStreamTrackPrivate; > >-class MediaRecorderPrivateMock final : public MediaRecorderPrivate { >+class WEBCORE_EXPORT MediaRecorderPrivateMock final : public MediaRecorderPrivate { > private: > void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) final; > void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final; >- Ref<Blob> fetchData() final; >+ RefPtr<SharedBuffer> fetchData() final; >+ const String& mimeType() final; > > void generateMockString(MediaStreamTrackPrivate&); > >diff --git a/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h b/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h >new file mode 100644 >index 0000000000000000000000000000000000000000..3c359fc434750dc54631c7bba083c9d44012a420 >--- /dev/null >+++ b/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h >@@ -0,0 +1,85 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED >+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE >+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY >+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES >+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; >+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON >+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS >+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#pragma once >+ >+#if ENABLE(MEDIA_STREAM) >+ >+#include "SharedBuffer.h" >+#include <wtf/Deque.h> >+#include <wtf/Lock.h> >+#include <wtf/RetainPtr.h> >+#include <wtf/ThreadSafeRefCounted.h> >+#include <wtf/threads/BinarySemaphore.h> >+ >+typedef struct opaqueCMSampleBuffer *CMSampleBufferRef; >+ >+OBJC_CLASS AVAssetWriter; >+OBJC_CLASS AVAssetWriterInput; >+ >+namespace WTF { >+class MediaTime; >+} >+ >+namespace WebCore { >+ >+class AudioStreamDescription; >+class PlatformAudioData; >+ >+class MediaRecorderPrivateWriter : public ThreadSafeRefCounted<MediaRecorderPrivateWriter, WTF::DestructionThread::Main> { >+public: >+ MediaRecorderPrivateWriter() = default; >+ >+ bool setupWriter(); >+ bool setVideoInput(int width, int height); >+ bool setAudioInput(); >+ bool isWriterReady() { return m_writer; } >+ void appendVideoSampleBuffer(CMSampleBufferRef); >+ void appendAudioSampleBuffer(const PlatformAudioData&, const AudioStreamDescription&, const WTF::MediaTime&, size_t); >+ void stopRecording(); >+ RefPtr<SharedBuffer> fetchData(); >+ >+private: >+ RetainPtr<AVAssetWriter> m_writer; >+ RetainPtr<AVAssetWriterInput> m_videoInput; >+ RetainPtr<AVAssetWriterInput> m_audioInput; >+ >+ String m_path; >+ Lock m_videoLock; >+ Lock m_audioLock; >+ BinarySemaphore m_finishWritingSemaphore; >+ BinarySemaphore m_finishWritingAudioSemaphore; >+ BinarySemaphore m_finishWritingVideoSemaphore; >+ bool m_hasStartedWriting { false }; >+ bool m_isStopped { false }; >+ bool m_isFirstAudioSample { true }; >+ dispatch_queue_t m_audioPullQueue; >+ dispatch_queue_t m_videoPullQueue; >+ Deque<RetainPtr<CMSampleBufferRef>> m_videoBufferPool; >+ Deque<RetainPtr<CMSampleBufferRef>> m_audioBufferPool; >+}; >+ >+} // namespace WebCore >+ >+#endif // ENABLE(MEDIA_STREAM) >diff --git a/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm b/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm >new file mode 100644 >index 0000000000000000000000000000000000000000..dc2b352700b1b1e594d38a9e6c3b01ad0d465026 >--- /dev/null >+++ b/Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm >@@ -0,0 +1,286 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >+ * THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#include "config.h" >+#include "MediaRecorderPrivateWriterCocoa.h" >+ >+#if ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION) >+ >+#include "AudioStreamDescription.h" >+#include "FileSystem.h" >+#include "Logging.h" >+#include "WebAudioBufferList.h" >+#include <AVFoundation/AVAssetWriter.h> >+#include <AVFoundation/AVAssetWriterInput.h> >+#include <pal/cf/CoreMediaSoftLink.h> >+ >+typedef AVAssetWriter AVAssetWriterType; >+typedef AVAssetWriterInput AVAssetWriterInputType; >+ >+SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation) >+ >+SOFT_LINK_CLASS(AVFoundation, AVAssetWriter) >+SOFT_LINK_CLASS(AVFoundation, AVAssetWriterInput) >+ >+SOFT_LINK_CONSTANT(AVFoundation, AVFileTypeMPEG4, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVVideoCodecKey, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVVideoCodecH264, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVVideoWidthKey, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVVideoHeightKey, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVMediaTypeVideo, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVMediaTypeAudio, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVEncoderBitRatePerChannelKey, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVFormatIDKey, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVNumberOfChannelsKey, NSString *) >+SOFT_LINK_CONSTANT(AVFoundation, AVSampleRateKey, NSString *) >+ >+#define AVFileTypeMPEG4 getAVFileTypeMPEG4() >+#define AVMediaTypeAudio getAVMediaTypeAudio() >+#define AVMediaTypeVideo getAVMediaTypeVideo() >+#define AVVideoCodecKey getAVVideoCodecKey() >+#define AVVideoCodecH264 getAVVideoCodecH264() >+#define AVVideoWidthKey getAVVideoWidthKey() >+#define AVVideoHeightKey getAVVideoHeightKey() >+#define AVEncoderBitRatePerChannelKey getAVEncoderBitRatePerChannelKey() >+#define AVFormatIDKey getAVFormatIDKey() >+#define AVNumberOfChannelsKey getAVNumberOfChannelsKey() >+#define AVSampleRateKey getAVSampleRateKey() >+ >+using namespace WebCore; >+ >+namespace WebCore { >+ >+using namespace PAL; >+ >+bool MediaRecorderPrivateWriter::setupWriter() >+{ >+ if (m_writer) >+ return false; >+ >+ NSString *directory = FileSystem::createTemporaryDirectory(@"videos"); >+ NSString *filename = [NSString stringWithFormat:@"/%lld.mp4", CMClockGetTime(CMClockGetHostTimeClock()).value]; >+ NSString *path = [directory stringByAppendingString:filename]; >+ >+ NSURL *outputURL = [NSURL fileURLWithPath:path]; >+ m_path = [path UTF8String]; >+ NSError *error = nil; >+ m_writer = adoptNS([allocAVAssetWriterInstance() initWithURL:outputURL fileType:AVFileTypeMPEG4 error:&error]); >+ if (error) { >+ RELEASE_LOG_ERROR(Media, "create AVAssetWriter instance failed with error code %ld", (long)error.code); >+ m_writer = nullptr; >+ return false; >+ } >+ return true; >+} >+ >+bool MediaRecorderPrivateWriter::setVideoInput(int width, int height) >+{ >+ if (m_videoInput) >+ return false; >+ NSDictionary *videoSettings = @{ AVVideoCodecKey: AVVideoCodecH264, AVVideoWidthKey: [NSNumber numberWithInt:width], AVVideoHeightKey: [NSNumber numberWithInt:height] }; >+ m_videoInput = adoptNS([allocAVAssetWriterInputInstance() initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings sourceFormatHint:nil]); >+ [m_videoInput setExpectsMediaDataInRealTime:true]; >+ >+ if (![m_writer canAddInput:m_videoInput.get()]) { >+ m_videoInput = nullptr; >+ RELEASE_LOG_ERROR(Media, "the video input is not allowed to add to the AVAssetWriter"); >+ return false; >+ } >+ [m_writer addInput:m_videoInput.get()]; >+ m_videoPullQueue = dispatch_queue_create("WebCoreVideoRecordingPullBufferQueue", DISPATCH_QUEUE_SERIAL); >+ return true; >+} >+ >+bool MediaRecorderPrivateWriter::setAudioInput() >+{ >+ if (m_audioInput) >+ return false; >+ NSDictionary *audioSettings = @{ AVEncoderBitRatePerChannelKey : @(28000), AVFormatIDKey : @(kAudioFormatMPEG4AAC), AVNumberOfChannelsKey : @(1), AVSampleRateKey : @(22050) }; >+ >+ m_audioInput = adoptNS([allocAVAssetWriterInputInstance() initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings sourceFormatHint:nil]); >+ [m_audioInput setExpectsMediaDataInRealTime:true]; >+ >+ if (![m_writer canAddInput:m_audioInput.get()]) { >+ m_audioInput = nullptr; >+ RELEASE_LOG_ERROR(Media, "the audio input is not allowed to add to the AVAssetWriter"); >+ return false; >+ } >+ [m_writer addInput:m_audioInput.get()]; >+ m_audioPullQueue = dispatch_queue_create("WebCoreAudioRecordingPullBufferQueue", DISPATCH_QUEUE_SERIAL); >+ return true; >+} >+ >+static inline CMSampleBufferRef copySampleBufferWithCurrentTimeStamp(CMSampleBufferRef originalBuffer) >+{ >+ CMTime startTime = CMClockGetTime(CMClockGetHostTimeClock()); >+ CMItemCount count = 0; >+ CMSampleBufferGetSampleTimingInfoArray(originalBuffer, 0, nil, &count); >+ >+ Vector<CMSampleTimingInfo> timeInfo(count); >+ CMSampleBufferGetSampleTimingInfoArray(originalBuffer, count, timeInfo.data(), &count); >+ >+ for (CMItemCount i = 0; i < count; i++) { >+ timeInfo[i].decodeTimeStamp = kCMTimeInvalid; >+ timeInfo[i].presentationTimeStamp = startTime; >+ } >+ >+ CMSampleBufferRef newBuffer; >+ CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, originalBuffer, count, timeInfo.data(), &newBuffer); >+ return newBuffer; >+} >+ >+void MediaRecorderPrivateWriter::appendVideoSampleBuffer(CMSampleBufferRef sampleBuffer) >+{ >+ if (m_isStopped || !m_videoInput) >+ return; >+ >+ if (!m_hasStartedWriting) { >+ if (![m_writer startWriting]) { >+ m_isStopped = true; >+ return; >+ } >+ CMTime startTime = CMClockGetTime(CMClockGetHostTimeClock()); >+ [m_writer startSessionAtSourceTime:startTime]; >+ m_hasStartedWriting = true; >+ RefPtr<MediaRecorderPrivateWriter> protectedThis = this; >+ [m_videoInput requestMediaDataWhenReadyOnQueue:m_videoPullQueue usingBlock:[this, protectedThis] { >+ do { >+ if (![m_videoInput isReadyForMoreMediaData]) >+ break; >+ auto locker = holdLock(m_videoLock); >+ if (m_videoBufferPool.isEmpty()) >+ break; >+ auto buffer = m_videoBufferPool.takeFirst(); >+ locker.unlockEarly(); >+ if (![m_videoInput appendSampleBuffer:buffer.get()]) >+ break; >+ } while (true); >+ if (m_isStopped && m_videoBufferPool.isEmpty()) { >+ [m_videoInput markAsFinished]; >+ m_finishWritingVideoSemaphore.signal(); >+ } >+ }]; >+ return; >+ } >+ CMSampleBufferRef bufferWithCurrentTime = copySampleBufferWithCurrentTimeStamp(sampleBuffer); >+ auto locker = holdLock(m_videoLock); >+ m_videoBufferPool.append(retainPtr(bufferWithCurrentTime)); >+} >+ >+void MediaRecorderPrivateWriter::appendAudioSampleBuffer(const PlatformAudioData& data, const AudioStreamDescription& description, const WTF::MediaTime&, size_t sampleCount) >+{ >+ if ((!m_hasStartedWriting && m_videoInput) || m_isStopped || !m_audioInput) >+ return; >+ CMSampleBufferRef sampleBuffer; >+ CMFormatDescriptionRef format; >+ OSStatus error; >+ auto& basicDescription = *WTF::get<const AudioStreamBasicDescription*>(description.platformDescription().description); >+ error = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &basicDescription, 0, NULL, 0, NULL, NULL, &format); >+ if (m_isFirstAudioSample) { >+ if (!m_videoInput) { >+ // audio-only recording. >+ if (![m_writer startWriting]) { >+ m_isStopped = true; >+ return; >+ } >+ [m_writer startSessionAtSourceTime:CMClockGetTime(CMClockGetHostTimeClock())]; >+ m_hasStartedWriting = true; >+ } >+ m_isFirstAudioSample = false; >+ RefPtr<MediaRecorderPrivateWriter> protectedThis = this; >+ [m_audioInput requestMediaDataWhenReadyOnQueue:m_audioPullQueue usingBlock:[this, protectedThis] { >+ do { >+ if (![m_audioInput isReadyForMoreMediaData]) >+ break; >+ auto locker = holdLock(m_audioLock); >+ if (m_audioBufferPool.isEmpty()) >+ break; >+ auto buffer = m_audioBufferPool.takeFirst(); >+ locker.unlockEarly(); >+ [m_audioInput appendSampleBuffer:buffer.get()]; >+ } while (true); >+ if (m_isStopped && m_audioBufferPool.isEmpty()) { >+ [m_audioInput markAsFinished]; >+ m_finishWritingAudioSemaphore.signal(); >+ } >+ }]; >+ } >+ CMTime startTime = CMClockGetTime(CMClockGetHostTimeClock()); >+ >+ error = CMAudioSampleBufferCreateWithPacketDescriptions(kCFAllocatorDefault, NULL, false, NULL, NULL, format, sampleCount, startTime, NULL, &sampleBuffer); >+ if (error) >+ return; >+ error = CMSampleBufferSetDataBufferFromAudioBufferList(sampleBuffer, kCFAllocatorDefault, kCFAllocatorDefault, 0, downcast<WebAudioBufferList>(data).list()); >+ if (error) >+ return; >+ >+ auto locker = holdLock(m_audioLock); >+ m_audioBufferPool.append(retainPtr(sampleBuffer)); >+} >+ >+void MediaRecorderPrivateWriter::stopRecording() >+{ >+ m_isStopped = true; >+ if (!m_hasStartedWriting) >+ return; >+ ASSERT([m_writer status] == AVAssetWriterStatusWriting); >+ if (m_videoInput) >+ m_finishWritingVideoSemaphore.wait(); >+ >+ if (m_audioInput) >+ m_finishWritingAudioSemaphore.wait(); >+ >+ [m_writer finishWritingWithCompletionHandler:^{ >+ m_isStopped = false; >+ m_hasStartedWriting = false; >+ m_isFirstAudioSample = true; >+ if (m_videoInput) { >+ m_videoInput.clear(); >+ m_videoInput = nullptr; >+ dispatch_release(m_videoPullQueue); >+ } >+ if (m_audioInput) { >+ m_audioInput.clear(); >+ m_audioInput = nullptr; >+ dispatch_release(m_audioPullQueue); >+ } >+ m_writer.clear(); >+ m_writer = nullptr; >+ m_finishWritingSemaphore.signal(); >+ }]; >+} >+ >+RefPtr<SharedBuffer> MediaRecorderPrivateWriter::fetchData() >+{ >+ if ((m_path.isEmpty() && !m_isStopped) || !m_hasStartedWriting) >+ return nullptr; >+ >+ m_finishWritingSemaphore.wait(); >+ return SharedBuffer::createWithContentsOfFile(m_path); >+} >+ >+} // namespace WebCore >+ >+#endif // ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION) >diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp >index c2829c2782228ff7e14d06e79166845336b011b2..63968f211a498a5baf8033886ee1efcaddf1dddd 100644 >--- a/Source/WebCore/testing/Internals.cpp >+++ b/Source/WebCore/testing/Internals.cpp >@@ -225,6 +225,8 @@ > #endif > > #if ENABLE(MEDIA_STREAM) >+#include "MediaRecorder.h" >+#include "MediaRecorderPrivateMock.h" > #include "MediaStream.h" > #include "MockRealtimeMediaSourceCenter.h" > #endif >@@ -1468,6 +1470,16 @@ void Internals::setMockMediaCaptureDevicesEnabled(bool enabled) > WebCore::DeprecatedGlobalSettings::setMockCaptureDevicesEnabled(enabled); > } > >+static std::unique_ptr<MediaRecorderPrivate> createRecorderMockSource() >+{ >+ return std::unique_ptr<MediaRecorderPrivateMock>(new MediaRecorderPrivateMock); >+} >+ >+void Internals::setCustomPrivateRecorderCreator() >+{ >+ WebCore::MediaRecorder::setCustomPrivateRecorderCreator(createRecorderMockSource); >+} >+ > #endif > > ExceptionOr<Ref<DOMRect>> Internals::absoluteCaretBounds() >diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h >index e5276a57e5e87e9856e5abf142b072a752d112c2..158b2c0cc7846c870cc5938dc7919fd7c433f5e2 100644 >--- a/Source/WebCore/testing/Internals.h >+++ b/Source/WebCore/testing/Internals.h >@@ -497,6 +497,7 @@ public: > > #if ENABLE(MEDIA_STREAM) > void setMockMediaCaptureDevicesEnabled(bool); >+ void setCustomPrivateRecorderCreator(); > #endif > > #if ENABLE(WEB_RTC) >diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl >index 04cc51d869b82082b855670077964dd689948328..6fc1e35916e7d5c129265e473611454933d23e82 100644 >--- a/Source/WebCore/testing/Internals.idl >+++ b/Source/WebCore/testing/Internals.idl >@@ -568,6 +568,7 @@ enum CompositingPolicy { > [Conditional=WIRELESS_PLAYBACK_TARGET] void setMockMediaPlaybackTargetPickerEnabled(boolean enabled); > [Conditional=WIRELESS_PLAYBACK_TARGET, MayThrowException] void setMockMediaPlaybackTargetPickerState(DOMString deviceName, DOMString deviceState); > [Conditional=MEDIA_STREAM] void setMockMediaCaptureDevicesEnabled(boolean enabled); >+ [Conditional=MEDIA_STREAM] void setCustomPrivateRecorderCreator(); > > [Conditional=WEB_RTC] void emulateRTCPeerConnectionPlatformEvent(RTCPeerConnection connection, DOMString action); > [Conditional=WEB_RTC] void useMockRTCPeerConnectionFactory(DOMString testCase); >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index 764d973f38ae53eb58bc24b8e961639e5f763ec0..e061ddf2960c52a25066bec48d848a4625cd503e 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,22 @@ >+2018-11-30 YUHAN WU <yuhan_wu@apple.com> >+ >+ Implement non-timeslice mode encoding for MediaRecorder >+ https://bugs.webkit.org/show_bug.cgi?id=192069 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Create new tests for encoding of MediaRecorder. Check if the produced video and audio are correct. >+ Add code to turn on the mock source of MediaRecorder for the two old tests because the real source is enabled by default. >+ >+ * http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable-expected.txt: Added. >+ * http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html: Added. >+ * http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt: Added. >+ * http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html: Added. >+ * http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable-expected.txt: Added. >+ * http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html: Added. >+ * http/wpt/mediarecorder/MediaRecorder-dataavailable.html: >+ * http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html: >+ > 2018-11-01 Chris Dumez <cdumez@apple.com> > > Location object sans browsing context >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable-expected.txt b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..4ace29c6c002daa7f91a725c582dd9ff423c967a >--- /dev/null >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS MediaRecorder can successfully record the audio for a audio-only stream >+ >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html >new file mode 100644 >index 0000000000000000000000000000000000000000..a43c4f8c7b0418c0ade3b1bad2a44558309753be >--- /dev/null >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html >@@ -0,0 +1,47 @@ >+<!doctype html> >+<html> >+<head> >+ <title>MediaRecorder Dataavailable</title> >+ <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder"> >+ <script src="/resources/testharness.js"></script> >+ <script src="/resources/testharnessreport.js"></script> >+</head> >+<body> >+<audio id="player"> >+</audio> >+<script> >+ >+ async_test(t => { >+ const ac = new AudioContext(); >+ const osc = ac.createOscillator(); >+ const dest = ac.createMediaStreamDestination(); >+ const audio = dest.stream; >+ osc.connect(dest); >+ const recorder = new MediaRecorder(audio); >+ recorder.ondataavailable = t.step_func(blobEvent => { >+ assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent'); >+ assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable'); >+ assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++'); >+ assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob'); >+ assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers'); >+ const player = document.getElementById("player"); >+ player.src = window.URL.createObjectURL(blobEvent.data); >+ player.oncanplay = () => { >+ assert_greater_than(player.duration, 0, 'the duration should be greater than 0'); >+ t.done(); >+ }; >+ player.load(); >+ }); >+ >+ recorder.start(); >+ osc.start(); >+ assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully'); >+ setTimeout(() => { >+ recorder.stop(); >+ osc.stop(); >+ }, 500); >+ }, 'MediaRecorder can successfully record the audio for a audio-only stream'); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..708c1285298e5345bc7cf9eef6816cf0f6215b2b >--- /dev/null >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt >@@ -0,0 +1,4 @@ >+ >+ >+PASS MediaRecorder can successfully record the video for a audio-video stream >+ >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html >new file mode 100644 >index 0000000000000000000000000000000000000000..df0436c9088398938984149e5835416745385c31 >--- /dev/null >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html >@@ -0,0 +1,113 @@ >+<!doctype html> >+<html> >+<head> >+ <title>MediaRecorder Dataavailable</title> >+ <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder"> >+ <script src="/resources/testharness.js"></script> >+ <script src="/resources/testharnessreport.js"></script> >+ <script src="../common/canvas-tests.js"></script> >+ <link rel="stylesheet" href="../common/canvas-tests.css"> >+</head> >+<body> >+<div> >+ <video id="player"> >+ </video> >+</div> >+<div> >+ <canvas id="canvas" width="200" height="200"> >+ </canvas> >+ <canvas id="frame" width="200" height="200"> >+ </canvas> >+</div> >+<script> >+ var context; >+ var drawStartTime; >+ >+ function createVideoStream() { >+ const canvas = document.getElementById("canvas"); >+ context = canvas.getContext('2d'); >+ return canvas.captureStream(); >+ } >+ >+ function doRedImageDraw() { >+ if (context) { >+ context.fillStyle = "#ff0000"; >+ context.fillRect(0, 0, 200, 200); >+ if (Date.now() - drawStartTime < 500) { >+ window.requestAnimationFrame(doRedImageDraw); >+ } else { >+ drawStartTime = Date.now(); >+ doGreenImageDraw(); >+ } >+ } >+ } >+ >+ function doGreenImageDraw() { >+ if (context) { >+ context.fillStyle = "#00ff00"; >+ context.fillRect(0, 0, 200, 200); >+ if (Date.now() - drawStartTime < 500) { >+ window.requestAnimationFrame(doGreenImageDraw); >+ } >+ } >+ } >+ >+ async_test(t => { >+ const ac = new AudioContext(); >+ const osc = ac.createOscillator(); >+ const dest = ac.createMediaStreamDestination(); >+ const audio = dest.stream; >+ osc.connect(dest); >+ >+ const video = createVideoStream(); >+ assert_equals(video.getAudioTracks().length, 0, "video mediastream starts with no audio track"); >+ assert_equals(audio.getAudioTracks().length, 1, "audio mediastream starts with one audio track"); >+ video.addTrack(audio.getAudioTracks()[0]); >+ assert_equals(video.getAudioTracks().length, 1, "video mediastream starts with one audio track"); >+ const recorder = new MediaRecorder(video); >+ let mode = 0; >+ >+ recorder.ondataavailable = t.step_func(blobEvent => { >+ assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent'); >+ assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable'); >+ assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++'); >+ assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob'); >+ assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers'); >+ player.src = window.URL.createObjectURL(blobEvent.data); >+ const resFrame = document.getElementById("frame"); >+ const resContext = resFrame.getContext('2d'); >+ player.oncanplay = () => { >+ assert_greater_than(player.duration, 0.1, 'the duration should be greater than 100ms'); >+ player.play(); >+ }; >+ player.onplay = () => { >+ player.pause(); >+ player.currentTime = 0.1; >+ }; >+ player.onseeked = () => { >+ resContext.drawImage(player, 0, 0); >+ if (!mode) { >+ _assertPixelApprox(resFrame, 0, 0, 255, 0, 0, 255, "0, 0", "255, 0, 0, 255", 5); >+ _assertPixelApprox(resFrame, 50, 50, 255, 0, 0, 255, "50, 50", "255, 0, 0, 255", 5); >+ mode = 1; >+ player.currentTime = player.duration; >+ } else { >+ _assertPixelApprox(resFrame, 199, 0, 0, 255, 0, 255, "199, 0", "0, 255, 0, 255", 5); >+ _assertPixelApprox(resFrame, 199, 199, 0, 255, 0, 255, "199, 199", "0, 255, 0, 255", 5); >+ t.done(); >+ } >+ }; >+ player.load(); >+ }); >+ drawStartTime = Date.now(); >+ doRedImageDraw(); >+ recorder.start(); >+ assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully'); >+ setTimeout(() => { >+ recorder.stop(); >+ }, 1000); >+ }, 'MediaRecorder can successfully record the video for a audio-video stream'); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable-expected.txt b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..d69a7be74e1b2e0fb15adf434e8368a7a21c0704 >--- /dev/null >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable-expected.txt >@@ -0,0 +1,4 @@ >+ >+ >+PASS MediaRecorder can successfully record the video for a video-only stream >+ >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html >new file mode 100644 >index 0000000000000000000000000000000000000000..2e644dc3c7f3c2b4bf7f97e6e907fb238349b374 >--- /dev/null >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html >@@ -0,0 +1,102 @@ >+<!doctype html> >+<html> >+<head> >+ <title>MediaRecorder Dataavailable</title> >+ <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder"> >+ <script src="/resources/testharness.js"></script> >+ <script src="/resources/testharnessreport.js"></script> >+ <script src="../common/canvas-tests.js"></script> >+ <link rel="stylesheet" href="../common/canvas-tests.css"> >+</head> >+<body> >+<div> >+ <video id="player"> >+ </video> >+</div> >+<div> >+ <canvas id="canvas" width="200" height="200"> >+ </canvas> >+ <canvas id="frame" width="200" height="200"> >+ </canvas> >+</div> >+<script> >+ var context; >+ var drawStartTime; >+ >+ function createVideoStream() { >+ const canvas = document.getElementById("canvas"); >+ context = canvas.getContext('2d'); >+ return canvas.captureStream(); >+ } >+ >+ function doRedImageDraw() { >+ if (context) { >+ context.fillStyle = "#ff0000"; >+ context.fillRect(0, 0, 200, 200); >+ if (Date.now() - drawStartTime < 500) { >+ window.requestAnimationFrame(doRedImageDraw); >+ } else { >+ drawStartTime = Date.now(); >+ doGreenImageDraw(); >+ } >+ } >+ } >+ >+ function doGreenImageDraw() { >+ if (context) { >+ context.fillStyle = "#00ff00"; >+ context.fillRect(0, 0, 200, 200); >+ if (Date.now() - drawStartTime < 500) { >+ window.requestAnimationFrame(doGreenImageDraw); >+ } >+ } >+ } >+ >+ async_test(t => { >+ const video = createVideoStream(); >+ const recorder = new MediaRecorder(video); >+ let mode = 0; >+ recorder.ondataavailable = t.step_func(blobEvent => { >+ assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent'); >+ assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable'); >+ assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++'); >+ assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob'); >+ assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers'); >+ player.src = window.URL.createObjectURL(blobEvent.data); >+ const resFrame = document.getElementById("frame"); >+ const resContext = resFrame.getContext('2d'); >+ player.oncanplay = () => { >+ assert_greater_than(player.duration, 0.1, 'the duration should be greater than 100ms'); >+ player.play(); >+ }; >+ player.onplay = () => { >+ player.pause(); >+ player.currentTime = 0.1; >+ }; >+ player.onseeked = () => { >+ resContext.drawImage(player, 0, 0); >+ if (!mode) { >+ _assertPixelApprox(resFrame, 0, 0, 255, 0, 0, 255, "0, 0", "255, 0, 0, 255", 5); >+ _assertPixelApprox(resFrame, 50, 50, 255, 0, 0, 255, "50, 50", "255, 0, 0, 255", 5); >+ mode = 1; >+ player.currentTime = player.duration; >+ } else { >+ _assertPixelApprox(resFrame, 199, 0, 0, 255, 0, 255, "199, 0", "0, 255, 0, 255", 5); >+ _assertPixelApprox(resFrame, 199, 199, 0, 255, 0, 255, "199, 199", "0, 255, 0, 255", 5); >+ t.done(); >+ } >+ }; >+ player.load(); >+ }); >+ drawStartTime = Date.now(); >+ doRedImageDraw(); >+ recorder.start(); >+ assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully'); >+ setTimeout(() => { >+ recorder.stop(); >+ }, 1000); >+ }, 'MediaRecorder can successfully record the video for a video-only stream'); >+ >+</script> >+</body> >+</html> >\ No newline at end of file >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html >index bdca7f7a40a137c18fb705230acffbd24e935ab9..4ac3d6d50bf17b682f4932bb0ea3aaafeefc903f 100644 >--- a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html >@@ -12,6 +12,9 @@ > <script> > var context; > >+ if (window.internals) >+ internals.setCustomPrivateRecorderCreator(); >+ > function createVideoStream() { > const canvas = document.getElementById("canvas"); > context = canvas.getContext('2d'); >@@ -44,7 +47,7 @@ > drawSomethingOnCanvas(); > setTimeout(() => { > recorder.stop(); >- }, 2000) >+ }, 1000) > }, 'MediaRecorder will fire a dataavailable event with a blob data for a video-only stream when stop() is called'); > > async_test(t => { >@@ -65,7 +68,7 @@ > setTimeout(() => { > recorder.stop(); > osc.stop(); >- }, 2000); >+ }, 1000); > }, 'MediaRecorder will fire a dataavailable event with a blob data for a audio-only stream when stop() is called'); > > async_test(t => { >@@ -93,7 +96,7 @@ > setTimeout(() => { > recorder.stop(); > osc.stop(); >- }, 2000); >+ }, 1000); > }, 'MediaRecorder will fire a dataavailable event with a blob data for a video-audio stream when stop() is called'); > > </script> >diff --git a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html >index 1286b8bde4094fd44037f89dc567f35a7605e056..43ee488752a4bb46e36464b4b048c4b64f3e9766 100644 >--- a/LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html >+++ b/LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html >@@ -12,6 +12,9 @@ > <script> > var context; > >+ if (window.internals) >+ internals.setCustomPrivateRecorderCreator(); >+ > function createVideoStream() { > const canvas = document.getElementById("canvas"); > context = canvas.getContext('2d'); >@@ -41,7 +44,7 @@ > drawSomethingOnCanvas(); > setTimeout(() => { > recorder.stop(); >- }, 2000); >+ }, 1000); > }, 'MediaRecorder will fire a dataavailable event which only contains video buffers for a video-only stream when stop() is called'); > > async_test(t => { >@@ -67,7 +70,7 @@ > setTimeout(() => { > recorder.stop(); > osc.stop(); >- }, 2000); >+ }, 1000); > }, 'MediaRecorder will fire a dataavailable event which only contains audio buffers for a audio-only stream when stop() is called'); > > async_test(t => { >@@ -100,7 +103,7 @@ > setTimeout(() => { > recorder.stop(); > osc.stop(); >- }, 2000); >+ }, 1000); > }, 'MediaRecorder will fire a dataavailable event which only contains both video and audio buffers when stop() is called'); > > </script>
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 192069
:
355845
|
355846
|
355851
|
355854
|
355855
|
355856
|
355857
|
355945
|
355954
|
356110
|
356112
|
356113
|
356118
|
356234
|
356237
|
356244
|
356405
|
356411
|
356419
|
356897
|
356898
|
356899
|
356906
|
356932
|
356936
|
357196
|
357198