Changeset 214806 in webkit


Ignore:
Timestamp:
Apr 3, 2017 9:59:41 AM (7 years ago)
Author:
commit-queue@webkit.org
Message:

captureStream is getting black frames with webgl canvas
https://bugs.webkit.org/show_bug.cgi?id=170325

Patch by Youenn Fablet <youenn@apple.com> on 2017-04-03
Reviewed by Dean Jackson.

Source/WebCore:

Test: fast/mediastream/captureStream/canvas3d.html

Changing the webgl context to save buffers in case the canvas is captured.
Adding a canvas changed notification in case of clear.
In the future, we might want to change this notification and do it when endPaint or similar is called.

Adding an Internals API to grab the RGBA equivalent of the next track frame.
For that purpose, adding a bunch of WEBCORE_EXPORT.

  • Modules/mediastream/CanvasCaptureMediaStreamTrack.cpp:

(WebCore::CanvasCaptureMediaStreamTrack::Source::Source): Adding constraints support so that track settings
getter actually transmits the width and height of the source.
(WebCore::CanvasCaptureMediaStreamTrack::Source::canvasChanged): ensuring webgl canvas context keep their drawing buffer.

  • Modules/mediastream/MediaStreamTrack.h:
  • bindings/js/JSDOMGuardedObject.h:
  • bindings/js/JSDOMPromise.h:

(WebCore::DeferredPromise::resolve):
(WebCore::DeferredPromise::reject):

  • dom/ActiveDOMCallback.h:
  • html/HTMLCanvasElement.cpp:

(WebCore::HTMLCanvasElement::captureStream):

  • html/ImageData.h:
  • html/ImageData.idl:
  • html/canvas/WebGLRenderingContext.cpp:

(WebCore::WebGLRenderingContext::clear): ensuring canvas observers get notified in case of clear calls.

  • html/canvas/WebGLRenderingContextBase.h:

(WebCore::WebGLRenderingContextBase::preserveDrawingBuffer): Added to allow canvas capture to update this property.

  • platform/MediaSample.h:

(WebCore::MediaSample::getRGBAImageData): Added for internals API.

  • platform/graphics/avfoundation/MediaSampleAVFObjC.h:
  • platform/graphics/avfoundation/objc/MediaSampleAVFObjC.mm:

(WebCore::MediaSampleAVFObjC::getRGBAImageData):

  • platform/graphics/cv/PixelBufferConformerCV.cpp:

(WebCore::PixelBufferConformerCV::convert): Helper routine for getRGBAImageData.

  • platform/graphics/cv/PixelBufferConformerCV.h:
  • platform/mediastream/RealtimeMediaSourceSettings.h:

(WebCore::RealtimeMediaSourceSettings::setSupportedConstraints):
(WebCore::RealtimeMediaSourceSettings::setSupportedConstraits): Deleted.

  • platform/mediastream/mac/AVMediaCaptureSource.mm:

(WebCore::AVMediaCaptureSource::initializeSettings):

  • platform/mediastream/openwebrtc/RealtimeAudioSourceOwr.h:
  • platform/mediastream/openwebrtc/RealtimeVideoSourceOwr.h:
  • platform/mock/MockRealtimeMediaSource.cpp:

(WebCore::MockRealtimeMediaSource::initializeSettings):

  • testing/Internals.cpp:

(WebCore::Internals::grabNextMediaStreamTrackFrame):
(WebCore::Internals::videoSampleAvailable):

  • testing/Internals.h:
  • testing/Internals.idl:

LayoutTests:

  • fast/mediastream/captureStream/canvas3d-expected.txt: Added.
  • fast/mediastream/captureStream/canvas3d.html: Added.
Location:
trunk
Files:
2 added
25 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r214787 r214806  
     12017-04-03  Youenn Fablet  <youenn@apple.com>
     2
     3        captureStream is getting black frames with webgl canvas
     4        https://bugs.webkit.org/show_bug.cgi?id=170325
     5
     6        Reviewed by Dean Jackson.
     7
     8        * fast/mediastream/captureStream/canvas3d-expected.txt: Added.
     9        * fast/mediastream/captureStream/canvas3d.html: Added.
     10
    1112017-04-03  Per Arne Vollan  <pvollan@apple.com>
    212
  • trunk/Source/WebCore/ChangeLog

    r214787 r214806  
     12017-04-03  Youenn Fablet  <youenn@apple.com>
     2
     3        captureStream is getting black frames with webgl canvas
     4        https://bugs.webkit.org/show_bug.cgi?id=170325
     5
     6        Reviewed by Dean Jackson.
     7
     8        Test: fast/mediastream/captureStream/canvas3d.html
     9
     10        Changing the webgl context to save buffers in case the canvas is captured.
     11        Adding a canvas changed notification in case of clear.
     12        In the future, we might want to change this notification and do it when endPaint or similar is called.
     13
     14        Adding an Internals API to grab the RGBA equivalent of the next track frame.
     15        For that purpose, adding a bunch of WEBCORE_EXPORT.
     16
     17        * Modules/mediastream/CanvasCaptureMediaStreamTrack.cpp:
     18        (WebCore::CanvasCaptureMediaStreamTrack::Source::Source): Adding constraints support so that track settings
     19        getter actually transmits the width and height of the source.
     20        (WebCore::CanvasCaptureMediaStreamTrack::Source::canvasChanged): ensuring webgl canvas context keep their drawing buffer.
     21        * Modules/mediastream/MediaStreamTrack.h:
     22        * bindings/js/JSDOMGuardedObject.h:
     23        * bindings/js/JSDOMPromise.h:
     24        (WebCore::DeferredPromise::resolve):
     25        (WebCore::DeferredPromise::reject):
     26        * dom/ActiveDOMCallback.h:
     27        * html/HTMLCanvasElement.cpp:
     28        (WebCore::HTMLCanvasElement::captureStream):
     29        * html/ImageData.h:
     30        * html/ImageData.idl:
     31        * html/canvas/WebGLRenderingContext.cpp:
     32        (WebCore::WebGLRenderingContext::clear): ensuring canvas observers get notified in case of clear calls.
     33        * html/canvas/WebGLRenderingContextBase.h:
     34        (WebCore::WebGLRenderingContextBase::preserveDrawingBuffer): Added to allow canvas capture to update this property.
     35        * platform/MediaSample.h:
     36        (WebCore::MediaSample::getRGBAImageData): Added for internals API.
     37        * platform/graphics/avfoundation/MediaSampleAVFObjC.h:
     38        * platform/graphics/avfoundation/objc/MediaSampleAVFObjC.mm:
     39        (WebCore::MediaSampleAVFObjC::getRGBAImageData):
     40        * platform/graphics/cv/PixelBufferConformerCV.cpp:
     41        (WebCore::PixelBufferConformerCV::convert): Helper routine for getRGBAImageData.
     42        * platform/graphics/cv/PixelBufferConformerCV.h:
     43        * platform/mediastream/RealtimeMediaSourceSettings.h:
     44        (WebCore::RealtimeMediaSourceSettings::setSupportedConstraints):
     45        (WebCore::RealtimeMediaSourceSettings::setSupportedConstraits): Deleted.
     46        * platform/mediastream/mac/AVMediaCaptureSource.mm:
     47        (WebCore::AVMediaCaptureSource::initializeSettings):
     48        * platform/mediastream/openwebrtc/RealtimeAudioSourceOwr.h:
     49        * platform/mediastream/openwebrtc/RealtimeVideoSourceOwr.h:
     50        * platform/mock/MockRealtimeMediaSource.cpp:
     51        (WebCore::MockRealtimeMediaSource::initializeSettings):
     52        * testing/Internals.cpp:
     53        (WebCore::Internals::grabNextMediaStreamTrackFrame):
     54        (WebCore::Internals::videoSampleAvailable):
     55        * testing/Internals.h:
     56        * testing/Internals.idl:
     57
    1582017-04-03  Per Arne Vollan  <pvollan@apple.com>
    259
  • trunk/Source/WebCore/Modules/mediastream/CanvasCaptureMediaStreamTrack.cpp

    r213972 r214806  
    2727
    2828#include "GraphicsContext.h"
     29#include "WebGLRenderingContextBase.h"
    2930
    3031#if ENABLE(MEDIA_STREAM)
     
    6869    m_settings.setWidth(canvas.width());
    6970    m_settings.setHeight(canvas.height());
     71    RealtimeMediaSourceSupportedConstraints constraints;
     72    constraints.setSupportsWidth(true);
     73    constraints.setSupportsHeight(true);
     74    m_settings.setSupportedConstraints(constraints);
    7075}
    7176
     
    125130    ASSERT_UNUSED(canvas, m_canvas == &canvas);
    126131
     132    // FIXME: We need to preserve drawing buffer as we are currently grabbing frames asynchronously.
     133    // We should instead add an anchor point for both 2d and 3d contexts where canvas will actually paint.
     134    // And call canvas observers from that point.
     135    if (canvas.renderingContext() && canvas.renderingContext()->isWebGL()) {
     136        auto& context = static_cast<WebGLRenderingContextBase&>(*canvas.renderingContext());
     137        if (!context.isPreservingDrawingBuffer()) {
     138            canvas.document().addConsoleMessage(MessageSource::JS, MessageLevel::Warning, ASCIILiteral("Turning drawing buffer preservation for the WebGL canvas being captured"));
     139            context.setPreserveDrawingBuffer(true);
     140        }
     141    }
     142
    127143    // FIXME: We should try to generate the frame at the time the screen is being updated.
    128144    if (m_canvasChangedTimer.isActive())
  • trunk/Source/WebCore/Modules/mediastream/MediaStreamTrack.h

    r214042 r214806  
    9090        String groupId;
    9191    };
    92     TrackSettings getSettings() const;
     92    WEBCORE_EXPORT TrackSettings getSettings() const;
    9393
    9494    struct TrackCapabilities {
  • trunk/Source/WebCore/bindings/js/JSDOMGuardedObject.h

    r213108 r214806  
    3434namespace WebCore {
    3535
    36 class DOMGuardedObject : public RefCounted<DOMGuardedObject>, public ActiveDOMCallback {
     36class WEBCORE_EXPORT DOMGuardedObject : public RefCounted<DOMGuardedObject>, public ActiveDOMCallback {
    3737public:
    3838    ~DOMGuardedObject();
  • trunk/Source/WebCore/bindings/js/JSDOMPromise.h

    r213108 r214806  
    8989    void reject(std::nullptr_t);
    9090    void reject(Exception&&);
    91     void reject(ExceptionCode, const String& = { });
     91    WEBCORE_EXPORT void reject(ExceptionCode, const String& = { });
    9292    void reject(const JSC::PrivateName&);
    9393
     
    123123    JSC::JSPromiseDeferred* deferred() const { return guarded(); }
    124124
    125     void callFunction(JSC::ExecState&, JSC::JSValue function, JSC::JSValue resolution);
     125    WEBCORE_EXPORT void callFunction(JSC::ExecState&, JSC::JSValue function, JSC::JSValue resolution);
    126126    void resolve(JSC::ExecState& state, JSC::JSValue resolution) { callFunction(state, deferred()->resolve(), resolution); }
    127127    void reject(JSC::ExecState& state, JSC::JSValue resolution) { callFunction(state, deferred()->reject(), resolution); }
     
    213213void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&&, ArrayBuffer*);
    214214void fulfillPromiseWithArrayBuffer(Ref<DeferredPromise>&&, const void*, size_t);
    215 void rejectPromiseWithExceptionIfAny(JSC::ExecState&, JSDOMGlobalObject&, JSC::JSPromiseDeferred&);
     215WEBCORE_EXPORT void rejectPromiseWithExceptionIfAny(JSC::ExecState&, JSDOMGlobalObject&, JSC::JSPromiseDeferred&);
    216216JSC::EncodedJSValue createRejectedPromiseWithTypeError(JSC::ExecState&, const String&);
    217217
  • trunk/Source/WebCore/dom/ActiveDOMCallback.h

    r210293 r214806  
    4747    virtual ~ActiveDOMCallback();
    4848
    49     bool canInvokeCallback() const;
     49    WEBCORE_EXPORT bool canInvokeCallback() const;
    5050};
    5151
  • trunk/Source/WebCore/html/HTMLCanvasElement.cpp

    r214017 r214806  
    604604    if (frameRequestRate && frameRequestRate.value() < 0)
    605605        return Exception(NOT_SUPPORTED_ERR, ASCIILiteral("frameRequestRate is negative"));
    606    
     606
    607607    auto track = CanvasCaptureMediaStreamTrack::create(context, *this, WTFMove(frameRequestRate));
    608608    auto stream =  MediaStream::create(context);
  • trunk/Source/WebCore/html/ImageData.h

    r208659 r214806  
    3535namespace WebCore {
    3636
    37 class ImageData : public RefCounted<ImageData> {
     37class WEBCORE_EXPORT ImageData : public RefCounted<ImageData> {
    3838public:
    3939    static ExceptionOr<Ref<ImageData>> create(unsigned sw, unsigned sh);
  • trunk/Source/WebCore/html/ImageData.idl

    r209005 r214806  
    3232    ConstructorMayThrowException,
    3333    CustomToJSObject,
     34    ExportMacro=WEBCORE_EXPORT,
    3435    Exposed=(Window,Worker),
    3536    ImplementationLacksVTable,
  • trunk/Source/WebCore/html/canvas/WebGLRenderingContext.cpp

    r214086 r214806  
    471471    if (!clearIfComposited(mask))
    472472        m_context->clear(mask);
    473     markContextChanged();
     473    markContextChangedAndNotifyCanvasObserver();
    474474}
    475475
  • trunk/Source/WebCore/html/canvas/WebGLRenderingContextBase.h

    r214017 r214806  
    208208    long long getVertexAttribOffset(GC3Duint index, GC3Denum pname);
    209209
     210    bool isPreservingDrawingBuffer() const { return m_attributes.preserveDrawingBuffer; }
     211    void setPreserveDrawingBuffer(bool value) { m_attributes.preserveDrawingBuffer = value; }
     212
    210213    virtual void hint(GC3Denum target, GC3Denum mode) = 0;
    211214    GC3Dboolean isBuffer(WebGLBuffer*);
  • trunk/Source/WebCore/platform/MediaSample.h

    r214120 r214806  
    2828
    2929#include "FloatSize.h"
     30#include <runtime/TypedArrays.h>
    3031#include <wtf/MediaTime.h>
    3132#include <wtf/RefCounted.h>
     
    7071    virtual Ref<MediaSample> createNonDisplayingCopy() const = 0;
    7172
     73    virtual RefPtr<JSC::Uint8ClampedArray> getRGBAImageData() const { return nullptr; }
     74
    7275    enum SampleFlags {
    7376        None = 0,
  • trunk/Source/WebCore/platform/graphics/avfoundation/MediaSampleAVFObjC.h

    r214120 r214806  
    4141    static RefPtr<MediaSampleAVFObjC> createImageSample(Ref<JSC::Uint8ClampedArray>&&, unsigned long width, unsigned long height);
    4242    static RefPtr<MediaSampleAVFObjC> createImageSample(Vector<uint8_t>&&, unsigned long width, unsigned long height);
     43
     44    RefPtr<JSC::Uint8ClampedArray> getRGBAImageData() const final;
    4345
    4446private:
  • trunk/Source/WebCore/platform/graphics/avfoundation/objc/MediaSampleAVFObjC.mm

    r213598 r214806  
    2727#import "MediaSampleAVFObjC.h"
    2828
     29#import "PixelBufferConformerCV.h"
     30#import <runtime/TypedArrayInlines.h>
    2931#import <wtf/PrintStream.h>
    3032
     
    266268}
    267269
    268 }
     270RefPtr<JSC::Uint8ClampedArray> MediaSampleAVFObjC::getRGBAImageData() const
     271{
     272    const OSType imageFormat = kCVPixelFormatType_32RGBA;
     273    RetainPtr<CFNumberRef> imageFormatNumber = adoptCF(CFNumberCreate(nullptr,  kCFNumberIntType,  &imageFormat));
     274
     275    RetainPtr<CFMutableDictionaryRef> conformerOptions = adoptCF(CFDictionaryCreateMutable(0, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
     276    CFDictionarySetValue(conformerOptions.get(), kCVPixelBufferPixelFormatTypeKey, imageFormatNumber.get());
     277    PixelBufferConformerCV pixelBufferConformer(conformerOptions.get());
     278
     279    auto pixelBuffer = static_cast<CVPixelBufferRef>(CMSampleBufferGetImageBuffer(m_sample.get()));
     280    auto rgbaPixelBuffer = pixelBufferConformer.convert(pixelBuffer);
     281    auto status = CVPixelBufferLockBaseAddress(rgbaPixelBuffer.get(), kCVPixelBufferLock_ReadOnly);
     282    ASSERT(status == noErr);
     283
     284    void* data = CVPixelBufferGetBaseAddressOfPlane(rgbaPixelBuffer.get(), 0);
     285    size_t byteLength = CVPixelBufferGetHeight(pixelBuffer) * CVPixelBufferGetWidth(pixelBuffer) * 4;
     286    auto result = JSC::Uint8ClampedArray::create(JSC::ArrayBuffer::create(data, byteLength), 0, byteLength);
     287
     288    status = CVPixelBufferUnlockBaseAddress(rgbaPixelBuffer.get(), kCVPixelBufferLock_ReadOnly);
     289    ASSERT(status == noErr);
     290
     291    return result;
     292}
     293
     294}
  • trunk/Source/WebCore/platform/graphics/cv/PixelBufferConformerCV.cpp

    r197375 r214806  
    7373}
    7474
     75RetainPtr<CVPixelBufferRef> PixelBufferConformerCV::convert(CVPixelBufferRef rawBuffer)
     76{
     77#if USE(VIDEOTOOLBOX)
     78    RetainPtr<CVPixelBufferRef> buffer { rawBuffer };
     79
     80    if (!VTPixelBufferConformerIsConformantPixelBuffer(m_pixelConformer.get(), buffer.get())) {
     81        CVPixelBufferRef outputBuffer = nullptr;
     82        OSStatus status = VTPixelBufferConformerCopyConformedPixelBuffer(m_pixelConformer.get(), buffer.get(), false, &outputBuffer);
     83        if (status != noErr || !outputBuffer)
     84            return nullptr;
     85        return adoptCF(outputBuffer);
     86    }
     87#else
     88    UNUSED_PARAM(rawBuffer);
     89#endif
     90    return nullptr;
     91}
     92
    7593RetainPtr<CGImageRef> PixelBufferConformerCV::createImageFromPixelBuffer(CVPixelBufferRef rawBuffer)
    7694{
  • trunk/Source/WebCore/platform/graphics/cv/PixelBufferConformerCV.h

    r197375 r214806  
    3838public:
    3939    PixelBufferConformerCV(CFDictionaryRef attributes);
     40    RetainPtr<CVPixelBufferRef> convert(CVPixelBufferRef);
    4041    RetainPtr<CGImageRef> createImageFromPixelBuffer(CVPixelBufferRef);
    4142
  • trunk/Source/WebCore/platform/mediastream/RealtimeMediaSourceSettings.h

    r213936 r214806  
    9393    void setGroupId(const AtomicString& groupId) { m_groupId = groupId; }
    9494
    95     void setSupportedConstraits(const RealtimeMediaSourceSupportedConstraints& supportedConstraints) { m_supportedConstraints = supportedConstraints; }
     95    void setSupportedConstraints(const RealtimeMediaSourceSupportedConstraints& supportedConstraints) { m_supportedConstraints = supportedConstraints; }
    9696
    9797    template<class Encoder> void encode(Encoder&) const;
  • trunk/Source/WebCore/platform/mediastream/mac/AVMediaCaptureSource.mm

    r213880 r214806  
    205205{
    206206    if (m_currentSettings.deviceId().isEmpty())
    207         m_currentSettings.setSupportedConstraits(supportedConstraints());
     207        m_currentSettings.setSupportedConstraints(supportedConstraints());
    208208
    209209    m_currentSettings.setDeviceId(id());
  • trunk/Source/WebCore/platform/mediastream/openwebrtc/RealtimeAudioSourceOwr.h

    r210499 r214806  
    6060    void initializeSettings() final {
    6161        if (m_currentSettings.deviceId().isEmpty())
    62             m_currentSettings.setSupportedConstraits(supportedConstraints());
     62            m_currentSettings.setSupportedConstraints(supportedConstraints());
    6363
    6464        m_currentSettings.setDeviceId(id());
  • trunk/Source/WebCore/platform/mediastream/openwebrtc/RealtimeVideoSourceOwr.h

    r210499 r214806  
    6060    void initializeSettings() final {
    6161        if (m_currentSettings.deviceId().isEmpty())
    62             m_currentSettings.setSupportedConstraits(supportedConstraints());
     62            m_currentSettings.setSupportedConstraints(supportedConstraints());
    6363
    6464        m_currentSettings.setDeviceId(id());
  • trunk/Source/WebCore/platform/mock/MockRealtimeMediaSource.cpp

    r214178 r214806  
    9999{
    100100    if (m_currentSettings.deviceId().isEmpty()) {
    101         m_currentSettings.setSupportedConstraits(supportedConstraints());
     101        m_currentSettings.setSupportedConstraints(supportedConstraints());
    102102        m_currentSettings.setDeviceId(id());
    103103    }
  • trunk/Source/WebCore/testing/Internals.cpp

    r214713 r214806  
    8686#include "IntRect.h"
    8787#include "InternalSettings.h"
     88#include "JSImageData.h"
    8889#include "Language.h"
    8990#include "LibWebRTCProvider.h"
     
    38963897    m_track->source().addObserver(*this);
    38973898}
     3899
     3900void Internals::grabNextMediaStreamTrackFrame(TrackFramePromise&& promise)
     3901{
     3902    m_nextTrackFramePromise = WTFMove(promise);
     3903}
     3904
     3905void Internals::videoSampleAvailable(MediaSample& sample)
     3906{
     3907    m_trackVideoSampleCount++;
     3908    if (!m_nextTrackFramePromise)
     3909        return;
     3910
     3911    auto videoSettings = m_track->getSettings();
     3912    if (!videoSettings.width || !videoSettings.height)
     3913        return;
     3914
     3915    auto rgba = sample.getRGBAImageData();
     3916    if (!rgba)
     3917        return;
     3918    auto imageData = ImageData::create(rgba.releaseNonNull(), *videoSettings.width, *videoSettings.height);
     3919    if (!imageData.hasException())
     3920        m_nextTrackFramePromise->resolve(imageData.releaseReturnValue().releaseNonNull());
     3921    else
     3922        m_nextTrackFramePromise->reject(imageData.exception().code());
     3923    m_nextTrackFramePromise = std::nullopt;
     3924}
     3925
    38983926#endif
    38993927
  • trunk/Source/WebCore/testing/Internals.h

    r214713 r214806  
    3030#include "ContextDestructionObserver.h"
    3131#include "ExceptionOr.h"
     32#include "JSDOMPromise.h"
    3233#include "PageConsoleClient.h"
    3334#include "RealtimeMediaSource.h"
    3435#include <runtime/Float32Array.h>
     36#include <wtf/Optional.h>
    3537
    3638#if ENABLE(MEDIA_SESSION)
     
    5557class HTMLMediaElement;
    5658class HTMLSelectElement;
     59class ImageData;
    5760class InspectorStubFrontend;
    5861class InternalSettings;
     
    563566    unsigned long trackVideoSampleCount() const { return m_trackVideoSampleCount; }
    564567    void observeMediaStreamTrack(MediaStreamTrack&);
     568    using TrackFramePromise = DOMPromise<IDLInterface<ImageData>>;
     569    void grabNextMediaStreamTrackFrame(TrackFramePromise&&);
    565570#endif
    566571
     
    574579    // RealtimeMediaSource::Observer API
    575580#if ENABLE(MEDIA_STREAM)
    576     void videoSampleAvailable(MediaSample&) final { m_trackVideoSampleCount++; }
     581    void videoSampleAvailable(MediaSample&) final;
    577582    void audioSamplesAvailable(const MediaTime&, const PlatformAudioData&, const AudioStreamDescription&, size_t) final { m_trackAudioSampleCount++; }
    578583
     
    580585    unsigned long m_trackAudioSampleCount { 0 };
    581586    RefPtr<MediaStreamTrack> m_track;
     587    std::optional<TrackFramePromise> m_nextTrackFramePromise;
    582588#endif
    583589
  • trunk/Source/WebCore/testing/Internals.idl

    r214713 r214806  
    527527
    528528    [Conditional=MEDIA_STREAM] void observeMediaStreamTrack(MediaStreamTrack track);
     529    [Conditional=MEDIA_STREAM] Promise<ImageData> grabNextMediaStreamTrackFrame();
    529530    [Conditional=MEDIA_STREAM] readonly attribute unsigned long trackAudioSampleCount;
    530531    [Conditional=MEDIA_STREAM] readonly attribute unsigned long trackVideoSampleCount;
Note: See TracChangeset for help on using the changeset viewer.