Source/WebCore/ChangeLog

 12018-11-30 YUHAN WU <yuhan_wu@apple.com>
 2
 3 Implement non-timeslice mode encoding for MediaRecorder
 4 https://bugs.webkit.org/show_bug.cgi?id=192069
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Implement the encoding for non-timeslice mode of MediaRecorder.
 9 It only supports to record MP4 file through H264 and AAC encoding, we will need to support more MIME types and encoding methods.
 10 Add a API in internals to allow testings to turn on the mock source.
 11
 12 Tests: http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html
 13 http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html
 14 http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html
 15
 16 * Modules/mediarecorder/MediaRecorder.cpp:
 17 (WebCore::MediaRecorder::create):
 18 (WebCore::MediaRecorder::setCustomPrivateRecorderCreator):
 19 (WebCore::MediaRecorder::getPrivateImpl):
 20 (WebCore::MediaRecorder::MediaRecorder):
 21 (WebCore::MediaRecorder::stopRecording):
 22 (WebCore::MediaRecorder::stopRecordingInternal):
 23 (WebCore::MediaRecorder::createRecordingDataBlob):
 24 (WebCore::MediaRecorder::scheduleDeferredTask):
 25 * Modules/mediarecorder/MediaRecorder.h:
 26 * Modules/mediarecorder/MediaRecorder.idl:
 27 * SourcesCocoa.txt:
 28 * WebCore.xcodeproj/project.pbxproj:
 29 * platform/mediarecorder/MediaRecorderPrivate.h:
 30 (WebCore::MediaRecorderPrivate::stopRecording):
 31 * platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp: Added.
 32 (WebCore::MediaRecorderPrivateAVFImpl::create):
 33 (WebCore::MediaRecorderPrivateAVFImpl::MediaRecorderPrivateAVImpl):
 34 (WebCore::MediaRecorderPrivateAVFImpl::sampleBufferUpdated):
 35 (WebCore::MediaRecorderPrivateAVFImpl::audioSamplesAvailable):
 36 (WebCore::MediaRecorderPrivateAVFImpl::stopRecording):
 37 (WebCore::MediaRecorderPrivateAVFImpl::fetchData):
 38 (WebCore::MediaRecorderPrivateAVFImpl::mimeType):
 39 * platform/mediarecorder/MediaRecorderPrivateAVFImpl.h: Added.
 40 * platform/mediarecorder/MediaRecorderPrivateMock.cpp:
 41 (WebCore::MediaRecorderPrivateMock::fetchData):
 42 (WebCore::MediaRecorderPrivateMock::mimeType):
 43 * platform/mediarecorder/MediaRecorderPrivateMock.h:
 44 * platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h: Added.
 45 * platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm: Added.
 46 (WebCore::MediaRecorderPrivateWriter::setupWriter):
 47 (WebCore::MediaRecorderPrivateWriter::setVideoInput):
 48 (WebCore::MediaRecorderPrivateWriter::setAudioInput):
 49 (WebCore::copySampleBufferWithCurrentTimeStamp):
 50 (WebCore::MediaRecorderPrivateWriter::appendVideoSampleBuffer):
 51 (WebCore::MediaRecorderPrivateWriter::appendAudioSampleBuffer):
 52 (WebCore::MediaRecorderPrivateWriter::stopRecording):
 53 (WebCore::MediaRecorderPrivateWriter::fetchData):
 54 * testing/Internals.cpp:
 55 (WebCore::createRecorderMockSource):
 56 (WebCore::Internals::setCustomPrivateRecorderCreator):
 57 * testing/Internals.h:
 58 * testing/Internals.idl:
 59
1602018-11-01 Chris Dumez <cdumez@apple.com>
261
362 Location object sans browsing context

Source/WebCore/Modules/mediarecorder/MediaRecorder.cpp

3333#include "Document.h"
3434#include "EventNames.h"
3535#include "MediaRecorderErrorEvent.h"
 36#if PLATFORM(COCOA)
 37#include "MediaRecorderPrivateAVFImpl.h"
 38#endif
3639#include "MediaRecorderPrivateMock.h"
 40#include "SharedBuffer.h"
3741
3842namespace WebCore {
3943
40 Ref<MediaRecorder> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options)
 44creatorFunction MediaRecorder::m_customCreator = nullptr;
 45
 46ExceptionOr<Ref<MediaRecorder>> MediaRecorder::create(Document& document, Ref<MediaStream>&& stream, Options&& options)
4147{
4248 auto recorder = adoptRef(*new MediaRecorder(document, WTFMove(stream), WTFMove(options)));
4349 recorder->suspendIfNeeded();
44  return recorder;
 50 if (!recorder->m_private)
 51 return Exception { NotSupportedError, "The MediaRecorder is unsupported on this platform"_s };
 52 return WTFMove(recorder);
 53}
 54
 55void MediaRecorder::setCustomPrivateRecorderCreator(creatorFunction creator)
 56{
 57 m_customCreator = creator;
 58}
 59
 60std::unique_ptr<MediaRecorderPrivate> MediaRecorder::getPrivateImpl(const MediaStreamPrivate& stream)
 61{
 62 if (m_customCreator)
 63 return m_customCreator();
 64
 65#if PLATFORM(COCOA)
 66 return MediaRecorderPrivateAVFImpl::create(stream);
 67#endif
 68 return nullptr;
4569}
4670
4771MediaRecorder::MediaRecorder(Document& document, Ref<MediaStream>&& stream, Options&& option)
4872 : ActiveDOMObject(&document)
4973 , m_options(WTFMove(option))
5074 , m_stream(WTFMove(stream))
51  , m_private(makeUniqueRef<MediaRecorderPrivateMock>()) // FIXME: we will need to decide which MediaRecorderPrivate instance to create based on the mock enabled feature flag
 75 , m_private(MediaRecorder::getPrivateImpl(m_stream->privateStream()))
5276{
5377 m_tracks = WTF::map(m_stream->getTracks(), [] (auto&& track) -> Ref<MediaStreamTrackPrivate> {
5478 return track->privateTrack();

@@ExceptionOr<void> MediaRecorder::stopRecording()
95119{
96120 if (state() == RecordingState::Inactive)
97121 return Exception { InvalidStateError, "The MediaRecorder's state cannot be inactive"_s };
98 
 122
99123 scheduleDeferredTask([this] {
100124 if (!m_isActive || state() == RecordingState::Inactive)
101125 return;
102126
103127 stopRecordingInternal();
104128 ASSERT(m_state == RecordingState::Inactive);
105  dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, m_private->fetchData()));
 129 dispatchEvent(BlobEvent::create(eventNames().dataavailableEvent, Event::CanBubble::No, Event::IsCancelable::No, createRecordingDataBlob()));
106130 if (!m_isActive)
107131 return;
108132 dispatchEvent(Event::create(eventNames().stopEvent, Event::CanBubble::No, Event::IsCancelable::No));

@@void MediaRecorder::stopRecordingInternal()
119143 track->removeObserver(*this);
120144
121145 m_state = RecordingState::Inactive;
 146 m_private->stopRecording();
 147}
 148
 149Ref<Blob> MediaRecorder::createRecordingDataBlob()
 150{
 151 auto data = m_private->fetchData();
 152 if (!data)
 153 return Blob::create();
 154 return Blob::create(*data, m_private->mimeType());
122155}
123156
124157void MediaRecorder::didAddOrRemoveTrack()

@@void MediaRecorder::scheduleDeferredTask(Function<void()>&& function)
164197 auto* scriptExecutionContext = this->scriptExecutionContext();
165198 if (!scriptExecutionContext)
166199 return;
 200
167201 scriptExecutionContext->postTask([protectedThis = makeRef(*this), function = WTFMove(function)] (auto&) {
168202 function();
169203 });

Source/WebCore/Modules/mediarecorder/MediaRecorder.h

3434
3535namespace WebCore {
3636
 37class Blob;
3738class Document;
3839class MediaRecorderPrivate;
3940
 41typedef std::unique_ptr<MediaRecorderPrivate>(*creatorFunction)();
 42
4043class MediaRecorder final
4144 : public ActiveDOMObject
4245 , public RefCounted<MediaRecorder>

@@public:
5659
5760 ~MediaRecorder();
5861
59  static Ref<MediaRecorder> create(Document&, Ref<MediaStream>&&, Options&& = { });
 62 static ExceptionOr<Ref<MediaRecorder>> create(Document&, Ref<MediaStream>&&, Options&& = { });
 63
 64 WEBCORE_EXPORT static void setCustomPrivateRecorderCreator(creatorFunction);
6065
6166 RecordingState state() const { return m_state; }
6267

@@public:
6974private:
7075 MediaRecorder(Document&, Ref<MediaStream>&&, Options&& = { });
7176
 77 static std::unique_ptr<MediaRecorderPrivate> getPrivateImpl(const MediaStreamPrivate&);
 78
 79 Ref<Blob> createRecordingDataBlob();
 80
7281 // EventTarget
7382 void refEventTarget() final { ref(); }
7483 void derefEventTarget() final { deref(); }

@@private:
96105 void scheduleDeferredTask(Function<void()>&&);
97106 void setNewRecordingState(RecordingState, Ref<Event>&&);
98107
 108 static creatorFunction m_customCreator;
 109
99110 Options m_options;
100111 Ref<MediaStream> m_stream;
101  UniqueRef<MediaRecorderPrivate> m_private;
 112 std::unique_ptr<MediaRecorderPrivate> m_private;
102113 RecordingState m_state { RecordingState::Inactive };
103114 Vector<Ref<MediaStreamTrackPrivate>> m_tracks;
104115
105116 bool m_isActive { true };
106117};
107 
 118
108119} // namespace WebCore
109120
110121#endif // ENABLE(MEDIA_STREAM)

Source/WebCore/Modules/mediarecorder/MediaRecorder.idl

@@enum RecordingState { "inactive", "recording", "paused" };
2929 Conditional=MEDIA_STREAM,
3030 Constructor(MediaStream stream, optional MediaRecorderOptions options),
3131 ConstructorCallWith=Document,
 32 ConstructorMayThrowException,
3233 EnabledAtRuntime=MediaRecorder,
3334 Exposed=Window
3435] interface MediaRecorder : EventTarget {

Source/WebCore/SourcesCocoa.txt

@@platform/mac/WebNSAttributedStringExtras.mm
473473platform/mac/WebPlaybackControlsManager.mm
474474platform/mac/WidgetMac.mm
475475
 476platform/mediarecorder/MediaRecorderPrivateAVFImpl.cpp
 477platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm
 478
476479platform/mediasession/mac/MediaSessionInterruptionProviderMac.mm
477480
478481platform/mediastream/ios/AVAudioSessionCaptureDevice.mm

Source/WebCore/WebCore.xcodeproj/project.pbxproj

13611361 4D3B00AB215D69A70076B983 /* MediaRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B00A9215D69A70076B983 /* MediaRecorder.h */; };
13621362 4D3B00AF215D6A690076B983 /* BlobEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B00AD215D6A690076B983 /* BlobEvent.h */; };
13631363 4D3B5016217E58B700665DB1 /* MediaRecorderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */; };
 1364 4D73F946218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h */; };
 1365 4D73F94E218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h in Headers */ = {isa = PBXBuildFile; fileRef = 4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */; };
13641366 4DB7130D216ECB4D0096A4DD /* MediaRecorderErrorEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 4DB7130C216EC2BD0096A4DD /* MediaRecorderErrorEvent.h */; };
13651367 4E1959220A39DABA00220FE5 /* MediaFeatureNames.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E1959200A39DABA00220FE5 /* MediaFeatureNames.h */; settings = {ATTRIBUTES = (Private, ); }; };
13661368 4E19592A0A39DACC00220FE5 /* MediaQuery.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E1959240A39DACC00220FE5 /* MediaQuery.h */; settings = {ATTRIBUTES = (Private, ); }; };

79227924 4D3B00A9215D69A70076B983 /* MediaRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MediaRecorder.h; sourceTree = "<group>"; };
79237925 4D3B00AD215D6A690076B983 /* BlobEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlobEvent.h; sourceTree = "<group>"; };
79247926 4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivate.h; sourceTree = "<group>"; };
 7927 4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateAVFImpl.h; sourceTree = "<group>"; };
 7928 4D73F945218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaRecorderPrivateAVFImpl.cpp; sourceTree = "<group>"; };
 7929 4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateWriterCocoa.h; sourceTree = "<group>"; };
 7930 4D73F94D218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = MediaRecorderPrivateWriterCocoa.mm; sourceTree = "<group>"; wrapsLines = 0; };
79257931 4D7EB3F4217C6AE600D64888 /* BlobEvent.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = BlobEvent.cpp; sourceTree = "<group>"; };
79267932 4D9F6B642182532B0092A9C5 /* MediaRecorderPrivateMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaRecorderPrivateMock.h; sourceTree = "<group>"; };
79277933 4D9F6B652182532B0092A9C5 /* MediaRecorderPrivateMock.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MediaRecorderPrivateMock.cpp; sourceTree = "<group>"; };

1805818064 4D3B5012217E58A300665DB1 /* mediarecorder */ = {
1805918065 isa = PBXGroup;
1806018066 children = (
 18067 4D3C05D421AF480900F2890A /* cocoa */,
1806118068 4D3B5014217E58B700665DB1 /* MediaRecorderPrivate.h */,
 18069 4D73F945218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.cpp */,
 18070 4D73F944218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h */,
1806218071 4D9F6B652182532B0092A9C5 /* MediaRecorderPrivateMock.cpp */,
1806318072 4D9F6B642182532B0092A9C5 /* MediaRecorderPrivateMock.h */,
1806418073 );
1806518074 path = mediarecorder;
1806618075 sourceTree = "<group>";
1806718076 };
 18077 4D3C05D421AF480900F2890A /* cocoa */ = {
 18078 isa = PBXGroup;
 18079 children = (
 18080 4D73F94C218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h */,
 18081 4D73F94D218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.mm */,
 18082 );
 18083 path = cocoa;
 18084 sourceTree = "<group>";
 18085 };
1806818086 510310421BA8C64C003329C0 /* client */ = {
1806918087 isa = PBXGroup;
1807018088 children = (

2996329981 4D3B00AB215D69A70076B983 /* MediaRecorder.h in Headers */,
2996429982 4DB7130D216ECB4D0096A4DD /* MediaRecorderErrorEvent.h in Headers */,
2996529983 4D3B5016217E58B700665DB1 /* MediaRecorderPrivate.h in Headers */,
 29984 4D73F946218BC5FA003A3ED6 /* MediaRecorderPrivateAVFImpl.h in Headers */,
 29985 4D73F94E218C4A87003A3ED6 /* MediaRecorderPrivateWriterCocoa.h in Headers */,
2996629986 C90843D01B18E47D00B68564 /* MediaRemoteControls.h in Headers */,
2996729987 CD8ACA8F1D23971900ECC59E /* MediaRemoteSoftLink.h in Headers */,
2996829988 CEEFCD7A19DB31F7003876D7 /* MediaResourceLoader.h in Headers */,

Source/WebCore/platform/mediarecorder/MediaRecorderPrivate.h

@@class MediaTime;
3333namespace WebCore {
3434
3535class AudioStreamDescription;
36 class Blob;
37 class PlatformAudioData;
3836class MediaSample;
3937class MediaStreamTrackPrivate;
 38class PlatformAudioData;
 39class SharedBuffer;
4040
4141class MediaRecorderPrivate {
4242public:
4343 virtual void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) = 0;
4444 virtual void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) = 0;
4545
46  virtual Ref<Blob> fetchData() = 0;
 46 virtual RefPtr<SharedBuffer> fetchData() = 0;
 47 virtual const String& mimeType() = 0;
4748 virtual ~MediaRecorderPrivate() = default;
 49 virtual void stopRecording() { }
4850};
4951
5052} // namespace WebCore

Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.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 "MediaRecorderPrivateAVFImpl.h"
 28
 29#if ENABLE(MEDIA_STREAM)
 30
 31#include "AudioStreamDescription.h"
 32#include "MediaSample.h"
 33#include "MediaStreamPrivate.h"
 34#include "SharedBuffer.h"
 35#include "WebAudioBufferList.h"
 36
 37namespace WebCore {
 38
 39std::unique_ptr<MediaRecorderPrivateAVFImpl> MediaRecorderPrivateAVFImpl::create(const MediaStreamPrivate& stream)
 40{
 41 auto AVImpl = std::unique_ptr<MediaRecorderPrivateAVFImpl>(new MediaRecorderPrivateAVFImpl(stream));
 42 if (!AVImpl->isWriterReady)
 43 return nullptr;
 44 return AVImpl;
 45}
 46
 47MediaRecorderPrivateAVFImpl::MediaRecorderPrivateAVFImpl(const MediaStreamPrivate& stream)
 48{
 49 if (!m_writer.setupWriter())
 50 return;
 51 auto tracks = stream.tracks();
 52 bool videoSelected = false;
 53 bool audioSelected = false;
 54 for (auto& track : tracks) {
 55 if (!track->enabled() || track->ended())
 56 continue;
 57 switch (track->type()) {
 58 case RealtimeMediaSource::Type::Video: {
 59 auto& settings = track->settings();
 60 if (videoSelected || !settings.supportsWidth() || !settings.supportsHeight())
 61 break;
 62 // FIXME: we will need to implement support for multiple video tracks, currently we only choose the first track as the recorded track.
 63 // FIXME: we would better to throw an exception to JavaScript if setVideoInput failed
 64 if (!m_writer.setVideoInput(settings.width(), settings.height()))
 65 return;
 66 m_recordedVideoTrackID = track->id();
 67 videoSelected = true;
 68 break;
 69 }
 70 case RealtimeMediaSource::Type::Audio: {
 71 if (audioSelected)
 72 break;
 73 // FIXME: we will need to implement support for multiple audio tracks, currently we only choose the first track as the recorded track.
 74 // FIXME: we would better to throw an exception to JavaScript if setAudioInput failed
 75 if (!m_writer.setAudioInput())
 76 return;
 77 m_recordedAudioTrackID = track->id();
 78 audioSelected = true;
 79 break;
 80 }
 81 case RealtimeMediaSource::Type::None:
 82 break;
 83 }
 84 }
 85 isWriterReady = true;
 86}
 87
 88void MediaRecorderPrivateAVFImpl::sampleBufferUpdated(MediaStreamTrackPrivate& track, MediaSample& sampleBuffer)
 89{
 90 if (track.id() != m_recordedVideoTrackID)
 91 return;
 92 m_writer.appendVideoSampleBuffer(sampleBuffer.platformSample().sample.cmSampleBuffer);
 93}
 94
 95void MediaRecorderPrivateAVFImpl::audioSamplesAvailable(MediaStreamTrackPrivate& track, const WTF::MediaTime& mediaTime, const PlatformAudioData& data, const AudioStreamDescription& description, size_t sampleCount)
 96{
 97 if (track.id() != m_recordedAudioTrackID)
 98 return;
 99 ASSERT(is<WebAudioBufferList>(data));
 100 ASSERT(description.platformDescription().type == PlatformDescription::CAAudioStreamBasicType);
 101 m_writer.appendAudioSampleBuffer(data, description, mediaTime, sampleCount);
 102}
 103
 104void MediaRecorderPrivateAVFImpl::stopRecording()
 105{
 106 m_writer.stopRecording();
 107}
 108
 109RefPtr<SharedBuffer> MediaRecorderPrivateAVFImpl::fetchData()
 110{
 111 return m_writer.fetchData();
 112}
 113
 114const String& MediaRecorderPrivateAVFImpl::mimeType()
 115{
 116 static NeverDestroyed<const String> mp4MimeType(MAKE_STATIC_STRING_IMPL("video/mp4"));
 117 // FIXME: we will need to support more MIME types.
 118 return mp4MimeType;
 119}
 120
 121} // namespace WebCore
 122
 123#endif // ENABLE(MEDIA_STREAM)

Source/WebCore/platform/mediarecorder/MediaRecorderPrivateAVFImpl.h

 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#pragma once
 26
 27#if ENABLE(MEDIA_STREAM)
 28
 29#include "MediaRecorderPrivate.h"
 30#include "MediaRecorderPrivateWriterCocoa.h"
 31
 32namespace WebCore {
 33
 34class MediaStreamPrivate;
 35
 36class MediaRecorderPrivateAVFImpl final : public MediaRecorderPrivate {
 37public:
 38 static std::unique_ptr<MediaRecorderPrivateAVFImpl> create(const MediaStreamPrivate&);
 39
 40private:
 41 explicit MediaRecorderPrivateAVFImpl(const MediaStreamPrivate&);
 42
 43 void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) final;
 44 void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final;
 45 RefPtr<SharedBuffer> fetchData() final;
 46 const String& mimeType() final;
 47 void stopRecording();
 48
 49 String m_recordedVideoTrackID;
 50 String m_recordedAudioTrackID;
 51
 52 MediaRecorderPrivateWriter m_writer;
 53
 54 bool isWriterReady { false };
 55};
 56
 57} // namespace WebCore
 58
 59#endif // ENABLE(MEDIA_STREAM)

Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.cpp

2828
2929#if ENABLE(MEDIA_STREAM)
3030
31 #include "Blob.h"
3231#include "MediaStreamTrackPrivate.h"
 32#include "SharedBuffer.h"
3333
3434namespace WebCore {
3535

@@void MediaRecorderPrivateMock::generateMockString(MediaStreamTrackPrivate& track
5656 m_buffer.append("\r\n---------\r\n");
5757}
5858
59 Ref<Blob> MediaRecorderPrivateMock::fetchData()
 59RefPtr<SharedBuffer> MediaRecorderPrivateMock::fetchData()
6060{
6161 auto locker = holdLock(m_bufferLock);
6262 Vector<uint8_t> value(m_buffer.length());
6363 memcpy(value.data(), m_buffer.characters8(), m_buffer.length());
6464 m_buffer.clear();
65  return Blob::create(WTFMove(value), "text/plain");
 65 return SharedBuffer::create(WTFMove(value));
 66}
 67
 68const String& MediaRecorderPrivateMock::mimeType()
 69{
 70 static NeverDestroyed<const String> textPlainMimeType(MAKE_STATIC_STRING_IMPL("text/plain"));
 71 return textPlainMimeType;
6672}
6773
6874} // namespace WebCore

Source/WebCore/platform/mediarecorder/MediaRecorderPrivateMock.h

3232
3333namespace WebCore {
3434
35 class Blob;
3635class MediaStreamTrackPrivate;
3736
38 class MediaRecorderPrivateMock final : public MediaRecorderPrivate {
 37class WEBCORE_EXPORT MediaRecorderPrivateMock final : public MediaRecorderPrivate {
3938private:
4039 void sampleBufferUpdated(MediaStreamTrackPrivate&, MediaSample&) final;
4140 void audioSamplesAvailable(MediaStreamTrackPrivate&, const WTF::MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final;
42  Ref<Blob> fetchData() final;
 41 RefPtr<SharedBuffer> fetchData() final;
 42 const String& mimeType() final;
4343
4444 void generateMockString(MediaStreamTrackPrivate&);
4545

Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.h

 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#pragma once
 26
 27#if ENABLE(MEDIA_STREAM)
 28
 29#include "SharedBuffer.h"
 30#include <wtf/Deque.h>
 31#include <wtf/Lock.h>
 32#include <wtf/RetainPtr.h>
 33#include <wtf/ThreadSafeRefCounted.h>
 34#include <wtf/threads/BinarySemaphore.h>
 35
 36typedef struct opaqueCMSampleBuffer *CMSampleBufferRef;
 37
 38OBJC_CLASS AVAssetWriter;
 39OBJC_CLASS AVAssetWriterInput;
 40
 41namespace WTF {
 42class MediaTime;
 43}
 44
 45namespace WebCore {
 46
 47class AudioStreamDescription;
 48class PlatformAudioData;
 49
 50class MediaRecorderPrivateWriter : public ThreadSafeRefCounted<MediaRecorderPrivateWriter, WTF::DestructionThread::Main> {
 51public:
 52 MediaRecorderPrivateWriter() = default;
 53
 54 bool setupWriter();
 55 bool setVideoInput(int width, int height);
 56 bool setAudioInput();
 57 void appendVideoSampleBuffer(CMSampleBufferRef);
 58 void appendAudioSampleBuffer(const PlatformAudioData&, const AudioStreamDescription&, const WTF::MediaTime&, size_t);
 59 void stopRecording();
 60 RefPtr<SharedBuffer> fetchData();
 61
 62private:
 63 RetainPtr<AVAssetWriter> m_writer;
 64 RetainPtr<AVAssetWriterInput> m_videoInput;
 65 RetainPtr<AVAssetWriterInput> m_audioInput;
 66
 67 String m_path;
 68 Lock m_videoLock;
 69 Lock m_audioLock;
 70 BinarySemaphore m_finishWritingSemaphore;
 71 BinarySemaphore m_finishWritingAudioSemaphore;
 72 BinarySemaphore m_finishWritingVideoSemaphore;
 73 bool m_hasStartedWriting { false };
 74 bool m_isStopped { false };
 75 bool m_isFirstAudioSample { true };
 76 dispatch_queue_t m_audioPullQueue;
 77 dispatch_queue_t m_videoPullQueue;
 78 Deque<RetainPtr<CMSampleBufferRef>> m_videoBufferPool;
 79 Deque<RetainPtr<CMSampleBufferRef>> m_audioBufferPool;
 80};
 81
 82} // namespace WebCore
 83
 84#endif // ENABLE(MEDIA_STREAM)

Source/WebCore/platform/mediarecorder/cocoa/MediaRecorderPrivateWriterCocoa.mm

 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''
 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 23 * THE POSSIBILITY OF SUCH DAMAGE.
 24 */
 25
 26#include "config.h"
 27#include "MediaRecorderPrivateWriterCocoa.h"
 28
 29#if ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION)
 30
 31#include "AudioStreamDescription.h"
 32#include "FileSystem.h"
 33#include "Logging.h"
 34#include "WebAudioBufferList.h"
 35#include <AVFoundation/AVAssetWriter.h>
 36#include <AVFoundation/AVAssetWriterInput.h>
 37#include <pal/cf/CoreMediaSoftLink.h>
 38
 39typedef AVAssetWriter AVAssetWriterType;
 40typedef AVAssetWriterInput AVAssetWriterInputType;
 41
 42SOFT_LINK_FRAMEWORK_OPTIONAL(AVFoundation)
 43
 44SOFT_LINK_CLASS(AVFoundation, AVAssetWriter)
 45SOFT_LINK_CLASS(AVFoundation, AVAssetWriterInput)
 46
 47SOFT_LINK_CONSTANT(AVFoundation, AVFileTypeMPEG4, NSString *)
 48SOFT_LINK_CONSTANT(AVFoundation, AVVideoCodecKey, NSString *)
 49SOFT_LINK_CONSTANT(AVFoundation, AVVideoCodecH264, NSString *)
 50SOFT_LINK_CONSTANT(AVFoundation, AVVideoWidthKey, NSString *)
 51SOFT_LINK_CONSTANT(AVFoundation, AVVideoHeightKey, NSString *)
 52SOFT_LINK_CONSTANT(AVFoundation, AVMediaTypeVideo, NSString *)
 53SOFT_LINK_CONSTANT(AVFoundation, AVMediaTypeAudio, NSString *)
 54SOFT_LINK_CONSTANT(AVFoundation, AVEncoderBitRatePerChannelKey, NSString *)
 55SOFT_LINK_CONSTANT(AVFoundation, AVFormatIDKey, NSString *)
 56SOFT_LINK_CONSTANT(AVFoundation, AVNumberOfChannelsKey, NSString *)
 57SOFT_LINK_CONSTANT(AVFoundation, AVSampleRateKey, NSString *)
 58
 59#define AVFileTypeMPEG4 getAVFileTypeMPEG4()
 60#define AVMediaTypeAudio getAVMediaTypeAudio()
 61#define AVMediaTypeVideo getAVMediaTypeVideo()
 62#define AVVideoCodecKey getAVVideoCodecKey()
 63#define AVVideoCodecH264 getAVVideoCodecH264()
 64#define AVVideoWidthKey getAVVideoWidthKey()
 65#define AVVideoHeightKey getAVVideoHeightKey()
 66#define AVEncoderBitRatePerChannelKey getAVEncoderBitRatePerChannelKey()
 67#define AVFormatIDKey getAVFormatIDKey()
 68#define AVNumberOfChannelsKey getAVNumberOfChannelsKey()
 69#define AVSampleRateKey getAVSampleRateKey()
 70
 71using namespace WebCore;
 72
 73namespace WebCore {
 74
 75using namespace PAL;
 76
 77bool MediaRecorderPrivateWriter::setupWriter()
 78{
 79 ASSERT(!m_writer);
 80
 81 NSString *directory = FileSystem::createTemporaryDirectory(@"videos");
 82 NSString *filename = [NSString stringWithFormat:@"/%lld.mp4", CMClockGetTime(CMClockGetHostTimeClock()).value];
 83 NSString *path = [directory stringByAppendingString:filename];
 84
 85 NSURL *outputURL = [NSURL fileURLWithPath:path];
 86 m_path = [path UTF8String];
 87 NSError *error = nil;
 88 m_writer = adoptNS([allocAVAssetWriterInstance() initWithURL:outputURL fileType:AVFileTypeMPEG4 error:&error]);
 89 if (error) {
 90 RELEASE_LOG_ERROR(MediaStream, "create AVAssetWriter instance failed with error code %ld", (long)error.code);
 91 m_writer = nullptr;
 92 return false;
 93 }
 94 return true;
 95}
 96
 97bool MediaRecorderPrivateWriter::setVideoInput(int width, int height)
 98{
 99 ASSERT(!m_videoInput);
 100
 101 NSDictionary *videoSettings = @{ AVVideoCodecKey: AVVideoCodecH264, AVVideoWidthKey: [NSNumber numberWithInt:width], AVVideoHeightKey: [NSNumber numberWithInt:height] };
 102 m_videoInput = adoptNS([allocAVAssetWriterInputInstance() initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings sourceFormatHint:nil]);
 103 [m_videoInput setExpectsMediaDataInRealTime:true];
 104
 105 if (![m_writer canAddInput:m_videoInput.get()]) {
 106 m_videoInput = nullptr;
 107 RELEASE_LOG_ERROR(MediaStream, "the video input is not allowed to add to the AVAssetWriter");
 108 return false;
 109 }
 110 [m_writer addInput:m_videoInput.get()];
 111 m_videoPullQueue = dispatch_queue_create("WebCoreVideoRecordingPullBufferQueue", DISPATCH_QUEUE_SERIAL);
 112 return true;
 113}
 114
 115bool MediaRecorderPrivateWriter::setAudioInput()
 116{
 117 ASSERT(!m_audioInput);
 118
 119 NSDictionary *audioSettings = @{ AVEncoderBitRatePerChannelKey : @(28000), AVFormatIDKey : @(kAudioFormatMPEG4AAC), AVNumberOfChannelsKey : @(1), AVSampleRateKey : @(22050) };
 120
 121 m_audioInput = adoptNS([allocAVAssetWriterInputInstance() initWithMediaType:AVMediaTypeAudio outputSettings:audioSettings sourceFormatHint:nil]);
 122 [m_audioInput setExpectsMediaDataInRealTime:true];
 123
 124 if (![m_writer canAddInput:m_audioInput.get()]) {
 125 m_audioInput = nullptr;
 126 RELEASE_LOG_ERROR(MediaStream, "the audio input is not allowed to add to the AVAssetWriter");
 127 return false;
 128 }
 129 [m_writer addInput:m_audioInput.get()];
 130 m_audioPullQueue = dispatch_queue_create("WebCoreAudioRecordingPullBufferQueue", DISPATCH_QUEUE_SERIAL);
 131 return true;
 132}
 133
 134static inline CMSampleBufferRef copySampleBufferWithCurrentTimeStamp(CMSampleBufferRef originalBuffer)
 135{
 136 CMTime startTime = CMClockGetTime(CMClockGetHostTimeClock());
 137 CMItemCount count = 0;
 138 CMSampleBufferGetSampleTimingInfoArray(originalBuffer, 0, nil, &count);
 139
 140 Vector<CMSampleTimingInfo> timeInfo(count);
 141 CMSampleBufferGetSampleTimingInfoArray(originalBuffer, count, timeInfo.data(), &count);
 142
 143 for (CMItemCount i = 0; i < count; i++) {
 144 timeInfo[i].decodeTimeStamp = kCMTimeInvalid;
 145 timeInfo[i].presentationTimeStamp = startTime;
 146 }
 147
 148 CMSampleBufferRef newBuffer;
 149 CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault, originalBuffer, count, timeInfo.data(), &newBuffer);
 150 return newBuffer;
 151}
 152
 153void MediaRecorderPrivateWriter::appendVideoSampleBuffer(CMSampleBufferRef sampleBuffer)
 154{
 155 ASSERT(m_videoInput);
 156 if (m_isStopped)
 157 return;
 158
 159 if (!m_hasStartedWriting) {
 160 if (![m_writer startWriting]) {
 161 m_isStopped = true;
 162 RELEASE_LOG_ERROR(MediaStream, "create AVAssetWriter instance failed with error code %ld", (long)[m_writer error]);
 163 return;
 164 }
 165 [m_writer startSessionAtSourceTime:CMClockGetTime(CMClockGetHostTimeClock())];
 166 m_hasStartedWriting = true;
 167 RefPtr<MediaRecorderPrivateWriter> protectedThis = this;
 168 [m_videoInput requestMediaDataWhenReadyOnQueue:m_videoPullQueue usingBlock:[this, protectedThis] {
 169 do {
 170 if (![m_videoInput isReadyForMoreMediaData])
 171 break;
 172 auto locker = holdLock(m_videoLock);
 173 if (m_videoBufferPool.isEmpty())
 174 break;
 175 auto buffer = m_videoBufferPool.takeFirst();
 176 locker.unlockEarly();
 177 if (![m_videoInput appendSampleBuffer:buffer.get()])
 178 break;
 179 } while (true);
 180 if (m_isStopped && m_videoBufferPool.isEmpty()) {
 181 [m_videoInput markAsFinished];
 182 m_finishWritingVideoSemaphore.signal();
 183 }
 184 }];
 185 return;
 186 }
 187 CMSampleBufferRef bufferWithCurrentTime = copySampleBufferWithCurrentTimeStamp(sampleBuffer);
 188 auto locker = holdLock(m_videoLock);
 189 m_videoBufferPool.append(retainPtr(bufferWithCurrentTime));
 190}
 191
 192void MediaRecorderPrivateWriter::appendAudioSampleBuffer(const PlatformAudioData& data, const AudioStreamDescription& description, const WTF::MediaTime&, size_t sampleCount)
 193{
 194 ASSERT(m_audioInput);
 195 if ((!m_hasStartedWriting && m_videoInput) || m_isStopped)
 196 return;
 197 CMSampleBufferRef sampleBuffer;
 198 CMFormatDescriptionRef format;
 199 OSStatus error;
 200 auto& basicDescription = *WTF::get<const AudioStreamBasicDescription*>(description.platformDescription().description);
 201 error = CMAudioFormatDescriptionCreate(kCFAllocatorDefault, &basicDescription, 0, NULL, 0, NULL, NULL, &format);
 202 if (m_isFirstAudioSample) {
 203 if (!m_videoInput) {
 204 // audio-only recording.
 205 if (![m_writer startWriting]) {
 206 m_isStopped = true;
 207 return;
 208 }
 209 [m_writer startSessionAtSourceTime:CMClockGetTime(CMClockGetHostTimeClock())];
 210 m_hasStartedWriting = true;
 211 }
 212 m_isFirstAudioSample = false;
 213 RefPtr<MediaRecorderPrivateWriter> protectedThis = this;
 214 [m_audioInput requestMediaDataWhenReadyOnQueue:m_audioPullQueue usingBlock:[this, protectedThis] {
 215 do {
 216 if (![m_audioInput isReadyForMoreMediaData])
 217 break;
 218 auto locker = holdLock(m_audioLock);
 219 if (m_audioBufferPool.isEmpty())
 220 break;
 221 auto buffer = m_audioBufferPool.takeFirst();
 222 locker.unlockEarly();
 223 [m_audioInput appendSampleBuffer:buffer.get()];
 224 } while (true);
 225 if (m_isStopped && m_audioBufferPool.isEmpty()) {
 226 [m_audioInput markAsFinished];
 227 m_finishWritingAudioSemaphore.signal();
 228 }
 229 }];
 230 }
 231 CMTime startTime = CMClockGetTime(CMClockGetHostTimeClock());
 232
 233 error = CMAudioSampleBufferCreateWithPacketDescriptions(kCFAllocatorDefault, NULL, false, NULL, NULL, format, sampleCount, startTime, NULL, &sampleBuffer);
 234 if (error)
 235 return;
 236 error = CMSampleBufferSetDataBufferFromAudioBufferList(sampleBuffer, kCFAllocatorDefault, kCFAllocatorDefault, 0, downcast<WebAudioBufferList>(data).list());
 237 if (error)
 238 return;
 239
 240 auto locker = holdLock(m_audioLock);
 241 m_audioBufferPool.append(retainPtr(sampleBuffer));
 242}
 243
 244void MediaRecorderPrivateWriter::stopRecording()
 245{
 246 m_isStopped = true;
 247 if (!m_hasStartedWriting)
 248 return;
 249 ASSERT([m_writer status] == AVAssetWriterStatusWriting);
 250 if (m_videoInput)
 251 m_finishWritingVideoSemaphore.wait();
 252
 253 if (m_audioInput)
 254 m_finishWritingAudioSemaphore.wait();
 255
 256 [m_writer finishWritingWithCompletionHandler:^{
 257 m_isStopped = false;
 258 m_hasStartedWriting = false;
 259 m_isFirstAudioSample = true;
 260 if (m_videoInput) {
 261 m_videoInput.clear();
 262 m_videoInput = nullptr;
 263 dispatch_release(m_videoPullQueue);
 264 }
 265 if (m_audioInput) {
 266 m_audioInput.clear();
 267 m_audioInput = nullptr;
 268 dispatch_release(m_audioPullQueue);
 269 }
 270 m_writer.clear();
 271 m_writer = nullptr;
 272 m_finishWritingSemaphore.signal();
 273 }];
 274}
 275
 276RefPtr<SharedBuffer> MediaRecorderPrivateWriter::fetchData()
 277{
 278 if ((m_path.isEmpty() && !m_isStopped) || !m_hasStartedWriting)
 279 return nullptr;
 280
 281 m_finishWritingSemaphore.wait();
 282 return SharedBuffer::createWithContentsOfFile(m_path);
 283}
 284
 285} // namespace WebCore
 286
 287#endif // ENABLE(MEDIA_STREAM) && USE(AVFOUNDATION)

Source/WebCore/testing/Internals.cpp

225225#endif
226226
227227#if ENABLE(MEDIA_STREAM)
 228#include "MediaRecorder.h"
 229#include "MediaRecorderPrivateMock.h"
228230#include "MediaStream.h"
229231#include "MockRealtimeMediaSourceCenter.h"
230232#endif

@@void Internals::setMockMediaCaptureDevicesEnabled(bool enabled)
14681470 WebCore::DeprecatedGlobalSettings::setMockCaptureDevicesEnabled(enabled);
14691471}
14701472
 1473static std::unique_ptr<MediaRecorderPrivate> createRecorderMockSource()
 1474{
 1475 return std::unique_ptr<MediaRecorderPrivateMock>(new MediaRecorderPrivateMock);
 1476}
 1477
 1478void Internals::setCustomPrivateRecorderCreator()
 1479{
 1480 WebCore::MediaRecorder::setCustomPrivateRecorderCreator(createRecorderMockSource);
 1481}
 1482
14711483#endif
14721484
14731485ExceptionOr<Ref<DOMRect>> Internals::absoluteCaretBounds()

Source/WebCore/testing/Internals.h

@@public:
497497
498498#if ENABLE(MEDIA_STREAM)
499499 void setMockMediaCaptureDevicesEnabled(bool);
 500 void setCustomPrivateRecorderCreator();
500501#endif
501502
502503#if ENABLE(WEB_RTC)

Source/WebCore/testing/Internals.idl

@@enum CompositingPolicy {
568568 [Conditional=WIRELESS_PLAYBACK_TARGET] void setMockMediaPlaybackTargetPickerEnabled(boolean enabled);
569569 [Conditional=WIRELESS_PLAYBACK_TARGET, MayThrowException] void setMockMediaPlaybackTargetPickerState(DOMString deviceName, DOMString deviceState);
570570 [Conditional=MEDIA_STREAM] void setMockMediaCaptureDevicesEnabled(boolean enabled);
 571 [Conditional=MEDIA_STREAM] void setCustomPrivateRecorderCreator();
571572
572573 [Conditional=WEB_RTC] void emulateRTCPeerConnectionPlatformEvent(RTCPeerConnection connection, DOMString action);
573574 [Conditional=WEB_RTC] void useMockRTCPeerConnectionFactory(DOMString testCase);

LayoutTests/ChangeLog

 12018-11-30 YUHAN WU <yuhan_wu@apple.com>
 2
 3 Implement non-timeslice mode encoding for MediaRecorder
 4 https://bugs.webkit.org/show_bug.cgi?id=192069
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Create new tests for encoding of MediaRecorder. Check if the produced video and audio are correct.
 9 Add code to turn on the mock source of MediaRecorder for the two old tests because the real source is enabled by default.
 10
 11 * http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable-expected.txt: Added.
 12 * http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html: Added.
 13 * http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt: Added.
 14 * http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html: Added.
 15 * http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable-expected.txt: Added.
 16 * http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html: Added.
 17 * http/wpt/mediarecorder/MediaRecorder-dataavailable.html:
 18 * http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html:
 19
1202018-11-01 Chris Dumez <cdumez@apple.com>
221
322 Location object sans browsing context

LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable-expected.txt

 1
 2PASS MediaRecorder can successfully record the audio for a audio-only stream
 3

LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-only-dataavailable.html

 1<!doctype html>
 2<html>
 3<head>
 4 <title>MediaRecorder Dataavailable</title>
 5 <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder">
 6 <script src="/resources/testharness.js"></script>
 7 <script src="/resources/testharnessreport.js"></script>
 8</head>
 9<body>
 10<audio id="player">
 11</audio>
 12<script>
 13
 14 async_test(t => {
 15 const ac = new AudioContext();
 16 const osc = ac.createOscillator();
 17 const dest = ac.createMediaStreamDestination();
 18 const audio = dest.stream;
 19 osc.connect(dest);
 20 const recorder = new MediaRecorder(audio);
 21 recorder.ondataavailable = t.step_func(blobEvent => {
 22 assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent');
 23 assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable');
 24 assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
 25 assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob');
 26 assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers');
 27 const player = document.getElementById("player");
 28 player.src = window.URL.createObjectURL(blobEvent.data);
 29 player.oncanplay = () => {
 30 assert_greater_than(player.duration, 0, 'the duration should be greater than 0');
 31 t.done();
 32 };
 33 player.load();
 34 });
 35
 36 recorder.start();
 37 osc.start();
 38 assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully');
 39 setTimeout(() => {
 40 recorder.stop();
 41 osc.stop();
 42 }, 500);
 43 }, 'MediaRecorder can successfully record the audio for a audio-only stream');
 44
 45</script>
 46</body>
 47</html>

LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable-expected.txt

 1
 2
 3PASS MediaRecorder can successfully record the video for a audio-video stream
 4

LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-audio-video-dataavailable.html

 1<!doctype html>
 2<html>
 3<head>
 4 <title>MediaRecorder Dataavailable</title>
 5 <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder">
 6 <script src="/resources/testharness.js"></script>
 7 <script src="/resources/testharnessreport.js"></script>
 8 <script src="../common/canvas-tests.js"></script>
 9 <link rel="stylesheet" href="../common/canvas-tests.css">
 10</head>
 11<body>
 12<div>
 13 <video id="player">
 14 </video>
 15</div>
 16<div>
 17 <canvas id="canvas" width="200" height="200">
 18 </canvas>
 19 <canvas id="frame" width="200" height="200">
 20 </canvas>
 21</div>
 22<script>
 23 var context;
 24 var drawStartTime;
 25
 26 function createVideoStream() {
 27 const canvas = document.getElementById("canvas");
 28 context = canvas.getContext('2d');
 29 return canvas.captureStream();
 30 }
 31
 32 function doRedImageDraw() {
 33 if (context) {
 34 context.fillStyle = "#ff0000";
 35 context.fillRect(0, 0, 200, 200);
 36 if (Date.now() - drawStartTime < 500) {
 37 window.requestAnimationFrame(doRedImageDraw);
 38 } else {
 39 drawStartTime = Date.now();
 40 doGreenImageDraw();
 41 }
 42 }
 43 }
 44
 45 function doGreenImageDraw() {
 46 if (context) {
 47 context.fillStyle = "#00ff00";
 48 context.fillRect(0, 0, 200, 200);
 49 if (Date.now() - drawStartTime < 500) {
 50 window.requestAnimationFrame(doGreenImageDraw);
 51 }
 52 }
 53 }
 54
 55 async_test(t => {
 56 const ac = new AudioContext();
 57 const osc = ac.createOscillator();
 58 const dest = ac.createMediaStreamDestination();
 59 const audio = dest.stream;
 60 osc.connect(dest);
 61
 62 const video = createVideoStream();
 63 assert_equals(video.getAudioTracks().length, 0, "video mediastream starts with no audio track");
 64 assert_equals(audio.getAudioTracks().length, 1, "audio mediastream starts with one audio track");
 65 video.addTrack(audio.getAudioTracks()[0]);
 66 assert_equals(video.getAudioTracks().length, 1, "video mediastream starts with one audio track");
 67 const recorder = new MediaRecorder(video);
 68 let mode = 0;
 69
 70 recorder.ondataavailable = t.step_func(blobEvent => {
 71 assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent');
 72 assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable');
 73 assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
 74 assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob');
 75 assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers');
 76 player.src = window.URL.createObjectURL(blobEvent.data);
 77 const resFrame = document.getElementById("frame");
 78 const resContext = resFrame.getContext('2d');
 79 player.oncanplay = () => {
 80 assert_greater_than(player.duration, 0.1, 'the duration should be greater than 100ms');
 81 player.play();
 82 };
 83 player.onplay = () => {
 84 player.pause();
 85 player.currentTime = 0.1;
 86 };
 87 player.onseeked = () => {
 88 resContext.drawImage(player, 0, 0);
 89 if (!mode) {
 90 _assertPixelApprox(resFrame, 0, 0, 255, 0, 0, 255, "0, 0", "255, 0, 0, 255", 5);
 91 _assertPixelApprox(resFrame, 50, 50, 255, 0, 0, 255, "50, 50", "255, 0, 0, 255", 5);
 92 mode = 1;
 93 player.currentTime = player.duration;
 94 } else {
 95 _assertPixelApprox(resFrame, 199, 0, 0, 255, 0, 255, "199, 0", "0, 255, 0, 255", 5);
 96 _assertPixelApprox(resFrame, 199, 199, 0, 255, 0, 255, "199, 199", "0, 255, 0, 255", 5);
 97 t.done();
 98 }
 99 };
 100 player.load();
 101 });
 102 drawStartTime = Date.now();
 103 doRedImageDraw();
 104 recorder.start();
 105 assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully');
 106 setTimeout(() => {
 107 recorder.stop();
 108 }, 1000);
 109 }, 'MediaRecorder can successfully record the video for a audio-video stream');
 110
 111</script>
 112</body>
 113</html>

LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable-expected.txt

 1
 2
 3PASS MediaRecorder can successfully record the video for a video-only stream
 4

LayoutTests/http/wpt/mediarecorder/MediaRecorder-AV-video-only-dataavailable.html

 1<!doctype html>
 2<html>
 3<head>
 4 <title>MediaRecorder Dataavailable</title>
 5 <link rel="help" href="https://w3c.github.io/mediacapture-record/MediaRecorder.html#mediarecorder">
 6 <script src="/resources/testharness.js"></script>
 7 <script src="/resources/testharnessreport.js"></script>
 8 <script src="../common/canvas-tests.js"></script>
 9 <link rel="stylesheet" href="../common/canvas-tests.css">
 10</head>
 11<body>
 12<div>
 13 <video id="player">
 14 </video>
 15</div>
 16<div>
 17 <canvas id="canvas" width="200" height="200">
 18 </canvas>
 19 <canvas id="frame" width="200" height="200">
 20 </canvas>
 21</div>
 22<script>
 23 var context;
 24 var drawStartTime;
 25
 26 function createVideoStream() {
 27 const canvas = document.getElementById("canvas");
 28 context = canvas.getContext('2d');
 29 return canvas.captureStream();
 30 }
 31
 32 function doRedImageDraw() {
 33 if (context) {
 34 context.fillStyle = "#ff0000";
 35 context.fillRect(0, 0, 200, 200);
 36 if (Date.now() - drawStartTime < 500) {
 37 window.requestAnimationFrame(doRedImageDraw);
 38 } else {
 39 drawStartTime = Date.now();
 40 doGreenImageDraw();
 41 }
 42 }
 43 }
 44
 45 function doGreenImageDraw() {
 46 if (context) {
 47 context.fillStyle = "#00ff00";
 48 context.fillRect(0, 0, 200, 200);
 49 if (Date.now() - drawStartTime < 500) {
 50 window.requestAnimationFrame(doGreenImageDraw);
 51 }
 52 }
 53 }
 54
 55 async_test(t => {
 56 const video = createVideoStream();
 57 const recorder = new MediaRecorder(video);
 58 let mode = 0;
 59 recorder.ondataavailable = t.step_func(blobEvent => {
 60 assert_true(blobEvent instanceof BlobEvent, 'the type of event should be BlobEvent');
 61 assert_equals(blobEvent.type, 'dataavailable', 'the event type should be dataavailable');
 62 assert_true(blobEvent.isTrusted, 'isTrusted should be true when the event is created by C++');
 63 assert_true(blobEvent.data instanceof Blob, 'the type of data should be Blob');
 64 assert_true(blobEvent.data.size > 0, 'the blob should contain some buffers');
 65 player.src = window.URL.createObjectURL(blobEvent.data);
 66 const resFrame = document.getElementById("frame");
 67 const resContext = resFrame.getContext('2d');
 68 player.oncanplay = () => {
 69 assert_greater_than(player.duration, 0.1, 'the duration should be greater than 100ms');
 70 player.play();
 71 };
 72 player.onplay = () => {
 73 player.pause();
 74 player.currentTime = 0.1;
 75 };
 76 player.onseeked = () => {
 77 resContext.drawImage(player, 0, 0);
 78 if (!mode) {
 79 _assertPixelApprox(resFrame, 0, 0, 255, 0, 0, 255, "0, 0", "255, 0, 0, 255", 5);
 80 _assertPixelApprox(resFrame, 50, 50, 255, 0, 0, 255, "50, 50", "255, 0, 0, 255", 5);
 81 mode = 1;
 82 player.currentTime = player.duration;
 83 } else {
 84 _assertPixelApprox(resFrame, 199, 0, 0, 255, 0, 255, "199, 0", "0, 255, 0, 255", 5);
 85 _assertPixelApprox(resFrame, 199, 199, 0, 255, 0, 255, "199, 199", "0, 255, 0, 255", 5);
 86 t.done();
 87 }
 88 };
 89 player.load();
 90 });
 91 drawStartTime = Date.now();
 92 doRedImageDraw();
 93 recorder.start();
 94 assert_equals(recorder.state, 'recording', 'MediaRecorder has been started successfully');
 95 setTimeout(() => {
 96 recorder.stop();
 97 }, 1000);
 98 }, 'MediaRecorder can successfully record the video for a video-only stream');
 99
 100</script>
 101</body>
 102</html>

LayoutTests/http/wpt/mediarecorder/MediaRecorder-dataavailable.html

1212<script>
1313 var context;
1414
 15 if (window.internals)
 16 internals.setCustomPrivateRecorderCreator();
 17
1518 function createVideoStream() {
1619 const canvas = document.getElementById("canvas");
1720 context = canvas.getContext('2d');

4447 drawSomethingOnCanvas();
4548 setTimeout(() => {
4649 recorder.stop();
47  }, 2000)
 50 }, 1000)
4851 }, 'MediaRecorder will fire a dataavailable event with a blob data for a video-only stream when stop() is called');
4952
5053 async_test(t => {

6568 setTimeout(() => {
6669 recorder.stop();
6770 osc.stop();
68  }, 2000);
 71 }, 1000);
6972 }, 'MediaRecorder will fire a dataavailable event with a blob data for a audio-only stream when stop() is called');
7073
7174 async_test(t => {

9396 setTimeout(() => {
9497 recorder.stop();
9598 osc.stop();
96  }, 2000);
 99 }, 1000);
97100 }, 'MediaRecorder will fire a dataavailable event with a blob data for a video-audio stream when stop() is called');
98101
99102</script>

LayoutTests/http/wpt/mediarecorder/MediaRecorder-mock-dataavailable.html

1212<script>
1313 var context;
1414
 15 if (window.internals)
 16 internals.setCustomPrivateRecorderCreator();
 17
1518 function createVideoStream() {
1619 const canvas = document.getElementById("canvas");
1720 context = canvas.getContext('2d');

4144 drawSomethingOnCanvas();
4245 setTimeout(() => {
4346 recorder.stop();
44  }, 2000);
 47 }, 1000);
4548 }, 'MediaRecorder will fire a dataavailable event which only contains video buffers for a video-only stream when stop() is called');
4649
4750 async_test(t => {

6770 setTimeout(() => {
6871 recorder.stop();
6972 osc.stop();
70  }, 2000);
 73 }, 1000);
7174 }, 'MediaRecorder will fire a dataavailable event which only contains audio buffers for a audio-only stream when stop() is called');
7275
7376 async_test(t => {

100103 setTimeout(() => {
101104 recorder.stop();
102105 osc.stop();
103  }, 2000);
 106 }, 1000);
104107 }, 'MediaRecorder will fire a dataavailable event which only contains both video and audio buffers when stop() is called');
105108
106109</script>