diff --git a/Source/WTF/wtf/MediaTime.cpp b/Source/WTF/wtf/MediaTime.cpp index 0582c95ea189f..7be8b98f9dbbe 100644 --- a/Source/WTF/wtf/MediaTime.cpp +++ b/Source/WTF/wtf/MediaTime.cpp @@ -374,7 +374,7 @@ MediaTime::operator bool() const return compare(zeroTime()) != EqualTo; } -MediaTime::ComparisonFlags MediaTime::compare(const MediaTime& rhs) const +MediaTime::ComparisonFlags MediaTime::compare(const MediaTime& rhs, bool fuzzy) const { if ((isPositiveInfinite() && rhs.isPositiveInfinite()) || (isNegativeInfinite() && rhs.isNegativeInfinite()) @@ -403,6 +403,8 @@ MediaTime::ComparisonFlags MediaTime::compare(const MediaTime& rhs) const if (hasDoubleValue() && rhs.hasDoubleValue()) { if (m_timeValueAsDouble == rhs.m_timeValueAsDouble) return EqualTo; + if (fuzzy && fabs(m_timeValueAsDouble - rhs.m_timeValueAsDouble) <= fuzzinessThreshold().toDouble()) + return EqualTo; return m_timeValueAsDouble < rhs.m_timeValueAsDouble ? LessThan : GreaterThan; } @@ -463,6 +465,12 @@ const MediaTime& MediaTime::indefiniteTime() return *time; } +const MediaTime& MediaTime::fuzzinessThreshold() +{ + static const MediaTime* time = new MediaTime(createWithDouble(0.00002)); + return *time; +} + void MediaTime::setTimeScale(int32_t timeScale) { if (hasDoubleValue()) { diff --git a/Source/WTF/wtf/MediaTime.h b/Source/WTF/wtf/MediaTime.h index a3feea841fa5f..70971bb6b9457 100644 --- a/Source/WTF/wtf/MediaTime.h +++ b/Source/WTF/wtf/MediaTime.h @@ -87,7 +87,7 @@ class WTF_EXPORT_PRIVATE MediaTime { GreaterThan = 1, } ComparisonFlags; - ComparisonFlags compare(const MediaTime& rhs) const; + ComparisonFlags compare(const MediaTime& rhs, bool fuzzy = false) const; bool isValid() const { return m_timeFlags & Valid; } bool isInvalid() const { return !isValid(); } @@ -102,6 +102,7 @@ class WTF_EXPORT_PRIVATE MediaTime { static const MediaTime& positiveInfiniteTime(); static const MediaTime& negativeInfiniteTime(); static const MediaTime& indefiniteTime(); + static const MediaTime& fuzzinessThreshold(); const int64_t& timeValue() const { return m_timeValue; } const int32_t& timeScale() const { return m_timeScale; } diff --git a/Source/WebCore/platform/graphics/PlatformTimeRanges.h b/Source/WebCore/platform/graphics/PlatformTimeRanges.h index 840d6f828f7a0..f3dc6337aa258 100644 --- a/Source/WebCore/platform/graphics/PlatformTimeRanges.h +++ b/Source/WebCore/platform/graphics/PlatformTimeRanges.h @@ -98,7 +98,7 @@ class PlatformTimeRanges { inline bool isContiguousWithRange(const Range& range) const { - return range.m_start == m_end || range.m_end == m_start; + return range.m_start.compare(m_end, true) == MediaTime::EqualTo || range.m_end.compare(m_start, true) == MediaTime::EqualTo; } inline Range unionWithOverlappingOrContiguousRange(const Range& range) const diff --git a/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h b/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h index 3e88a4dc5896d..21f0d13951892 100644 --- a/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h +++ b/Source/WebCore/platform/graphics/SourceBufferPrivateClient.h @@ -89,6 +89,8 @@ class SourceBufferPrivateClient { }; virtual void sourceBufferPrivateAppendComplete(SourceBufferPrivate*, AppendResult) = 0; virtual void sourceBufferPrivateDidReceiveRenderingError(SourceBufferPrivate*, int errocCode) = 0; + + virtual double timestampOffset() const = 0; }; } diff --git a/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.cpp index 2f0debcc087c6..4a9c6fd10cf42 100644 --- a/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.cpp @@ -37,6 +37,8 @@ AudioTrackPrivateGStreamer::AudioTrackPrivateGStreamer(GRefPtr playb : TrackPrivateBaseGStreamer(this, index, pad) , m_playbin(playbin) { + // FIXME: Get a real ID here + m_id = "A" + String::number(index); notifyTrackOfActiveChanged(); } diff --git a/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.h index 1775b202125a2..1213bd2753706 100644 --- a/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/AudioTrackPrivateGStreamer.h @@ -48,12 +48,14 @@ class AudioTrackPrivateGStreamer final : public AudioTrackPrivate, public TrackP virtual int trackIndex() const override { return m_index; } + virtual AtomicString id() const override { return m_id; } virtual AtomicString label() const override { return m_label; } virtual AtomicString language() const override { return m_language; } private: AudioTrackPrivateGStreamer(GRefPtr playbin, gint index, GRefPtr); + AtomicString m_id; GRefPtr m_playbin; }; diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp index d29f5a3939eaf..d4bd927a19c90 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.cpp @@ -333,6 +333,7 @@ MediaPlayerPrivateGStreamer::~MediaPlayerPrivateGStreamer() #if ENABLE(MEDIA_SOURCE) if (m_source && WEBKIT_IS_MEDIA_SRC(m_source.get())) { + webkit_media_src_set_mediaplayerprivate(WEBKIT_MEDIA_SRC(m_source.get()), 0); g_signal_handlers_disconnect_by_func(m_source.get(), reinterpret_cast(mediaPlayerPrivateVideoChangedCallback), this); g_signal_handlers_disconnect_by_func(m_source.get(), reinterpret_cast(mediaPlayerPrivateAudioChangedCallback), this); g_signal_handlers_disconnect_by_func(m_source.get(), reinterpret_cast(mediaPlayerPrivateTextChangedCallback), this); @@ -658,7 +659,11 @@ void MediaPlayerPrivateGStreamer::seek(float time) LOG_MEDIA_MESSAGE("[Seek] cannot seek, current state change is %s", gst_element_state_change_return_get_name(getStateResult)); return; } - if (getStateResult == GST_STATE_CHANGE_ASYNC || state < GST_STATE_PAUSED || m_isEndReached) { + if (getStateResult == GST_STATE_CHANGE_ASYNC || state < GST_STATE_PAUSED +#if ENABLE(MEDIA_SOURCE) + || (isMediaSource() && webkit_media_src_is_appending(WEBKIT_MEDIA_SRC(m_source.get()))) +#endif + || m_isEndReached) { m_seekIsPending = true; if (m_isEndReached) { LOG_MEDIA_MESSAGE("[Seek] reset pipeline"); @@ -683,11 +688,6 @@ bool MediaPlayerPrivateGStreamer::doSeek(gint64 position, float rate, GstSeekFla { gint64 startTime, endTime; - // TODO: Should do more than that, need to notify the media source - // and probably flush the pipeline at least. - if (isMediaSource()) - return true; - if (rate > 0) { startTime = position; endTime = GST_CLOCK_TIME_NONE; @@ -704,8 +704,23 @@ bool MediaPlayerPrivateGStreamer::doSeek(gint64 position, float rate, GstSeekFla if (!rate) rate = 1.0; - return gst_element_seek(m_pipeline.get(), rate, GST_FORMAT_TIME, seekType, - GST_SEEK_TYPE_SET, startTime, GST_SEEK_TYPE_SET, endTime); + MediaTime time(MediaTime::createWithDouble(double(static_cast(position) / GST_SECOND))); + +#if ENABLE(MEDIA_SOURCE) + if (isMediaSource()) + webkit_media_src_set_seek_time(WEBKIT_MEDIA_SRC(m_source.get()), time); +#endif + + if (!gst_element_seek(m_pipeline.get(), rate, GST_FORMAT_TIME, seekType, + GST_SEEK_TYPE_SET, startTime, GST_SEEK_TYPE_SET, endTime)) + return false; + +#if ENABLE(MEDIA_SOURCE) + if (isMediaSource()) + m_mediaSource->seekToTime(time); +#endif + + return true; } void MediaPlayerPrivateGStreamer::updatePlaybackRate() @@ -784,7 +799,9 @@ void MediaPlayerPrivateGStreamer::videoCapsChanged() void MediaPlayerPrivateGStreamer::notifyPlayerOfVideo() { gint numTracks = 0; +#if ENABLE(MEDIA_SOURCE) bool useMediaSource = false; +#endif if (m_pipeline) { #if ENABLE(MEDIA_SOURCE) if (m_mediaSource && WEBKIT_IS_MEDIA_SRC(m_source.get())) { @@ -862,7 +879,9 @@ void MediaPlayerPrivateGStreamer::audioChanged() void MediaPlayerPrivateGStreamer::notifyPlayerOfAudio() { gint numTracks = 0; +#if ENABLE(MEDIA_SOURCE) bool useMediaSource = false; +#endif if (m_pipeline) { #if ENABLE(MEDIA_SOURCE) if (m_mediaSource && WEBKIT_IS_MEDIA_SRC(m_source.get())) { @@ -935,7 +954,9 @@ void MediaPlayerPrivateGStreamer::textChanged() void MediaPlayerPrivateGStreamer::notifyPlayerOfText() { gint numTracks = 0; +#if ENABLE(MEDIA_SOURCE) bool useMediaSource = false; +#endif if (m_pipeline) { #if ENABLE(MEDIA_SOURCE) if (m_mediaSource && WEBKIT_IS_MEDIA_SRC(m_source.get())) { @@ -1112,6 +1133,56 @@ std::unique_ptr MediaPlayerPrivateGStreamer::buffered() cons return timeRanges; } +#if ENABLE(MEDIA_SOURCE) +static StreamType getStreamType(GstElement* element) +{ + g_return_val_if_fail(GST_IS_ELEMENT(element), STREAM_TYPE_UNKNOWN); + + GstIterator* it; + GValue item = G_VALUE_INIT; + StreamType result = STREAM_TYPE_UNKNOWN; + + it = gst_element_iterate_sink_pads(element); + + if (it && (gst_iterator_next(it, &item)) == GST_ITERATOR_OK) { + GstPad* pad = GST_PAD(g_value_get_object(&item)); + if (pad) { + GstCaps* caps = gst_pad_get_current_caps(pad); + if (caps && GST_IS_CAPS(caps)) { + const GstStructure* structure = gst_caps_get_structure(caps, 0); + if (structure) { + const gchar* mediatype = gst_structure_get_name(structure); + if (mediatype) { + // Look for "audio", "video", "text" + switch (mediatype[0]) { + case 'a': + result = STREAM_TYPE_AUDIO; + break; + case 'v': + result = STREAM_TYPE_VIDEO; + break; + case 't': + result = STREAM_TYPE_TEXT; + break; + default: + break; + } + } + } + gst_caps_unref(caps); + } + } + } + + g_value_unset(&item); + + if (it) + gst_iterator_free(it); + + return result; +} +#endif + void MediaPlayerPrivateGStreamer::handleSyncMessage(GstMessage* message) { // FIXME: Use proper formatting across USE(GSTREAMER_GL) and ENABLE(ENCRYPTED_MEDIA_V2). @@ -1357,6 +1428,15 @@ gboolean MediaPlayerPrivateGStreamer::handleMessage(GstMessage* message) case GST_MESSAGE_TOC: processTableOfContents(message); break; +#endif +#if ENABLE(MEDIA_SOURCE) + case GST_MESSAGE_RESET_TIME: + if (m_source && WEBKIT_IS_MEDIA_SRC(m_source.get())) { + StreamType streamType = getStreamType(GST_ELEMENT(GST_MESSAGE_SRC(message))); + if (streamType == STREAM_TYPE_AUDIO || streamType == STREAM_TYPE_VIDEO) + webkit_media_src_segment_needed(WEBKIT_MEDIA_SRC(m_source.get()), streamType); + } + break; #endif default: LOG_MEDIA_MESSAGE("Unhandled GStreamer message type: %s", @@ -1560,7 +1640,13 @@ float MediaPlayerPrivateGStreamer::maxTimeLoaded() const bool MediaPlayerPrivateGStreamer::didLoadingProgress() const { - if (!m_pipeline || !m_mediaDuration || (!isMediaSource() && !totalBytes())) + if (!m_pipeline || +#if ENABLE(MEDIA_SOURCE) + (!isMediaSource() && !totalBytes()) +#else + !totalBytes() +#endif + || !m_mediaDuration) return false; float currentMaxTimeLoaded = maxTimeLoaded(); bool didLoadingProgress = currentMaxTimeLoaded != m_maxTimeLoadedAtLastDidLoadingProgress; @@ -1637,6 +1723,7 @@ void MediaPlayerPrivateGStreamer::sourceChanged() g_signal_connect(m_source.get(), "video-changed", G_CALLBACK(mediaPlayerPrivateVideoChangedCallback), this); g_signal_connect(m_source.get(), "audio-changed", G_CALLBACK(mediaPlayerPrivateAudioChangedCallback), this); g_signal_connect(m_source.get(), "text-changed", G_CALLBACK(mediaPlayerPrivateTextChangedCallback), this); + webkit_media_src_set_mediaplayerprivate(WEBKIT_MEDIA_SRC(m_source.get()), this); } #endif } @@ -1985,6 +2072,20 @@ void MediaPlayerPrivateGStreamer::didEnd() } } +#if ENABLE(MEDIA_SOURCE) +void MediaPlayerPrivateGStreamer::notifyAppendComplete() +{ + if (m_seekIsPending) { + updatePlaybackRate(); + LOG_MEDIA_MESSAGE("[Seek] committing pending seek to %f after append completed", m_seekTime); + m_seekIsPending = false; + m_seeking = doSeek(toGstClockTime(m_seekTime), m_player->rate(), static_cast(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE)); + if (!m_seeking) + LOG_MEDIA_MESSAGE("[Seek] seeking to %f failed", m_seekTime); + } +} +#endif + void MediaPlayerPrivateGStreamer::cacheDuration() { if (m_mediaDuration || !m_mediaDurationKnown) @@ -2334,6 +2435,23 @@ GstElement* MediaPlayerPrivateGStreamer::audioSink() const GstElement* sink; g_object_get(m_pipeline.get(), "audio-sink", &sink, nullptr); return sink; + + GRefPtr playsink = adoptGRef(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "playsink")); + if (playsink) { + // The default value (0) means "send events to all the sinks", instead + // of "only to the first that returns true". This is needed for MSE seek. + g_object_set(G_OBJECT(playsink.get()), "send-event-mode", 0, NULL); + } +} + +void MediaPlayerPrivateGStreamer::configurePlaySink() +{ + GRefPtr playsink = adoptGRef(gst_bin_get_by_name(GST_BIN(m_pipeline.get()), "playsink")); + if (playsink) { + // The default value (0) means "send events to all the sinks", instead + // of "only to the first that returns true". This is needed for MSE seek. + g_object_set(G_OBJECT(playsink.get()), "send-event-mode", 0, NULL); + } } void MediaPlayerPrivateGStreamer::createGSTPlayBin() @@ -2389,6 +2507,7 @@ void MediaPlayerPrivateGStreamer::createGSTPlayBin() #endif g_object_set(m_pipeline.get(), "video-sink", createVideoSink(), "audio-sink", createAudioSink(), nullptr); + configurePlaySink(); // On 1.4.2 and newer we use the audio-filter property instead. // See https://bugzilla.gnome.org/show_bug.cgi?id=735748 for diff --git a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h index 8fc9251849eae..abe6cf74703a5 100644 --- a/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/MediaPlayerPrivateGStreamer.h @@ -137,6 +137,7 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateGStreamerBase { void sourceChanged(); GstElement* audioSink() const; + void configurePlaySink(); void setAudioStreamProperties(GObject*); @@ -157,6 +158,11 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateGStreamerBase { void signalDRM(); #endif + virtual bool isLiveStream() const { return m_isStreaming; } +#if ENABLE(MEDIA_SOURCE) + void notifyAppendComplete(); +#endif + private: static void getSupportedTypes(HashSet&); static MediaPlayer::SupportsType supportsType(const MediaEngineSupportParameters&); @@ -191,7 +197,6 @@ class MediaPlayerPrivateGStreamer : public MediaPlayerPrivateGStreamerBase { virtual String engineDescription() const { return "GStreamer"; } - virtual bool isLiveStream() const { return m_isStreaming; } virtual bool didPassCORSAccessCheck() const; virtual bool canSaveMediaData() const override; diff --git a/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.cpp index 3e392282545e2..5da38c985bb79 100644 --- a/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.cpp @@ -100,22 +100,21 @@ void SourceBufferPrivateGStreamer::setReadyState(MediaPlayer::ReadyState state) m_mediaSource->setReadyState(state); } -// TODO: Implement these -void SourceBufferPrivateGStreamer::flushAndEnqueueNonDisplayingSamples(Vector >, AtomicString) +void SourceBufferPrivateGStreamer::flushAndEnqueueNonDisplayingSamples(Vector > samples, AtomicString trackIDString) { - notImplemented(); + if (m_client) + m_client->flushAndEnqueueNonDisplayingSamples(samples, trackIDString); } -void SourceBufferPrivateGStreamer::enqueueSample(PassRefPtr, AtomicString) +void SourceBufferPrivateGStreamer::enqueueSample(PassRefPtr sample, AtomicString trackIDString) { - notImplemented(); + if (m_client) + m_client->enqueueSample(sample, trackIDString); } bool SourceBufferPrivateGStreamer::isReadyForMoreSamples(AtomicString) { - notImplemented(); - - return false; + return true; } void SourceBufferPrivateGStreamer::setActive(bool isActive) @@ -155,5 +154,13 @@ void SourceBufferPrivateGStreamer::didReceiveAllPendingSamples() } #endif +double SourceBufferPrivateGStreamer::timestampOffset() const +{ + if (m_sourceBufferPrivateClient) + return m_sourceBufferPrivateClient->timestampOffset(); + else + return 0.0; +} + } #endif diff --git a/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.h index d291a49028c41..6b269366a3ab4 100644 --- a/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/SourceBufferPrivateGStreamer.h @@ -67,6 +67,7 @@ class SourceBufferPrivateGStreamer final : public SourceBufferPrivate { virtual void notifyClientWhenReadyForMoreSamples(AtomicString) override; virtual bool isAborted() { return m_aborted; } virtual void resetAborted() { m_aborted = false; } + virtual double timestampOffset() const; private: SourceBufferPrivateGStreamer(MediaSourceGStreamer*, PassRefPtr, const ContentType&); diff --git a/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.cpp index e3652c350e3df..cfcdf00d04ab3 100644 --- a/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.cpp @@ -37,6 +37,8 @@ VideoTrackPrivateGStreamer::VideoTrackPrivateGStreamer(GRefPtr playb : TrackPrivateBaseGStreamer(this, index, pad) , m_playbin(playbin) { + // FIXME: Get a real ID here + m_id = "V" + String::number(index); notifyTrackOfActiveChanged(); } diff --git a/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.h index b216221e033f6..1f9f21584cc41 100644 --- a/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/VideoTrackPrivateGStreamer.h @@ -48,12 +48,14 @@ class VideoTrackPrivateGStreamer final : public VideoTrackPrivate, public TrackP virtual int trackIndex() const override { return m_index; } + virtual AtomicString id() const override { return m_id; } virtual AtomicString label() const override { return m_label; } virtual AtomicString language() const override { return m_language; } private: VideoTrackPrivateGStreamer(GRefPtr playbin, gint index, GRefPtr); + AtomicString m_id; GRefPtr m_playbin; }; diff --git a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp b/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp index ad52053ce80e2..9a200044cc72c 100644 --- a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp +++ b/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.cpp @@ -104,6 +104,7 @@ class GStreamerMediaSample : public MediaSample MediaTime m_pts, m_dts, m_duration; AtomicString m_trackID; size_t m_size; + GstBuffer* m_buffer; FloatSize m_presentationSize; MediaSample::SampleFlags m_flags; GStreamerMediaSample(GstBuffer* buffer, const FloatSize& presentationSize, const AtomicString& trackID) @@ -112,9 +113,13 @@ class GStreamerMediaSample : public MediaSample , m_dts(MediaTime::zeroTime()) , m_duration(MediaTime::zeroTime()) , m_trackID(trackID) + , m_size(0) + , m_buffer(0) , m_presentationSize(presentationSize) , m_flags(MediaSample::IsSync) { + if (!buffer) + return; if (GST_BUFFER_PTS_IS_VALID(buffer)) m_pts = MediaTime(GST_BUFFER_PTS(buffer), GST_SECOND); if (GST_BUFFER_DTS_IS_VALID(buffer)) @@ -122,6 +127,7 @@ class GStreamerMediaSample : public MediaSample if (GST_BUFFER_DURATION_IS_VALID(buffer)) m_duration = MediaTime(GST_BUFFER_DURATION(buffer), GST_SECOND); m_size = gst_buffer_get_size(buffer); + m_buffer = gst_buffer_ref(buffer); if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT)) m_flags = MediaSample::None; @@ -136,11 +142,28 @@ class GStreamerMediaSample : public MediaSample return adoptRef(new GStreamerMediaSample(buffer, presentationSize, trackID)); } + static PassRefPtr createFakeSample(MediaTime pts, MediaTime dts, MediaTime duration, const FloatSize& presentationSize, const AtomicString& trackID) + { + GStreamerMediaSample* s = new GStreamerMediaSample(0, presentationSize, trackID); + s->m_pts = pts; + s->m_dts = dts; + s->m_duration = duration; + s->m_flags = MediaSample::NonDisplaying; + return adoptRef(s); + } + + virtual ~GStreamerMediaSample() + { + if (m_buffer) + gst_buffer_unref(m_buffer); + } + MediaTime presentationTime() const { return m_pts; } MediaTime decodeTime() const { return m_dts; } MediaTime duration() const { return m_duration; } AtomicString trackID() const { return m_trackID; } size_t sizeInBytes() const { return m_size; } + GstBuffer* buffer() const { return m_buffer; } FloatSize presentationSize() const { return m_presentationSize; } SampleFlags flags() const { return m_flags; } PlatformSample platformSample() { return PlatformSample(); } @@ -156,8 +179,6 @@ typedef struct { WebCore::FloatSize presentationSize; } PendingReceiveSample; -typedef enum {STREAM_TYPE_UNKNOWN, STREAM_TYPE_AUDIO, STREAM_TYPE_VIDEO, STREAM_TYPE_TEXT} StreamType; - struct _Stream { Source* parent; @@ -208,6 +229,10 @@ struct _Source { // we must detect when no more samples have arrived after a while gint64 lastSampleTime; guint pendingSamplesAfterInitSegment; + + // To know how to construct the segment that will bind new data coming + // from appendBuffer. + MediaTime nextSamplePts; }; struct _WebKitMediaSrcPrivate @@ -223,6 +248,16 @@ struct _WebKitMediaSrcPrivate bool noMorePads; int numberOfPads; + gboolean paused; + gboolean seekable; + guint64 offset; + guint64 requestedOffset; + MediaTime seekTime; + int flushAndReenqueueCount; + GstEvent* seekEvent; + gboolean isAppending; + + WebCore::MediaPlayerPrivateGStreamer* mediaPlayerPrivate; WebCore::MediaSourceClientGStreamer* mediaSourceClient; }; @@ -259,6 +294,20 @@ static void webKitMediaSrcGetProperty(GObject*, guint propertyId, GValue*, GPara static GstStateChangeReturn webKitMediaSrcChangeState(GstElement*, GstStateChange); static gboolean webKitMediaSrcQueryWithParent(GstPad*, GstObject*, GstQuery*); static gboolean webKitMediaSrcEventWithParent(GstPad*, GstObject*, GstEvent*); +static gboolean webKitMediaSrcDemuxerEventWithParent(GstPad*, GstObject*, GstEvent*); +static gboolean webKitMediaSrcSeekDataCb(GstAppSrc*, guint64 offset, gpointer userData); + +static Stream* getStreamByDemuxerPad(WebKitMediaSrc* src, const GstPad* demuxersrcpad); +static GstClockTime toGstClockTime(float); + +static void webkit_media_src_set_appending(WebKitMediaSrc*, gboolean); + +static GstAppSrcCallbacks appsrcCallbacks = { + 0, + 0, + webKitMediaSrcSeekDataCb, + { 0 } +}; #define webkit_media_src_parent_class parent_class // We split this out into another macro to avoid a check-webkit-style error. @@ -325,6 +374,8 @@ static void webkit_media_src_class_init(WebKitMediaSrcClass* klass) static void webkit_media_src_init(WebKitMediaSrc* src) { src->priv = WEBKIT_MEDIA_SRC_GET_PRIVATE(src); + src->priv->seekTime = MediaTime::invalidTime(); + src->priv->isAppending = FALSE; } static void webKitMediaSrcFinalize(GObject* object) @@ -335,6 +386,16 @@ static void webKitMediaSrcFinalize(GObject* object) // TODO: Free sources g_free(priv->location); + priv->seekTime = MediaTime::invalidTime(); + + if (priv->seekEvent) { + gst_event_unref(priv->seekEvent); + priv->seekEvent = 0; + } + + if (priv->mediaPlayerPrivate) + priv->mediaPlayerPrivate = 0; + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); } @@ -524,6 +585,107 @@ static gboolean webKitMediaSrcEventWithParent(GstPad* pad, GstObject* parent, Gs return result; } +static gboolean webKitMediaSrcDemuxerSinkEventWithParent(GstPad* pad, GstObject* parent, GstEvent* event) +{ + gboolean result = FALSE; + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_FLUSH_STOP: { + result = gst_pad_event_default(pad, parent, event); + + if (!result) + break; + + WebKitMediaSrc* src = NULL; + MediaTime nextSamplePts = MediaTime::invalidTime(); + + if (parent) + src = WEBKIT_MEDIA_SRC(GST_ELEMENT_PARENT(GST_ELEMENT(parent))); + + if (src) { + GST_OBJECT_LOCK(src); + + Source* source = NULL; + for (GList* sources = src->priv->sources; sources; sources = sources->next) { + Source* s = static_cast(sources->data); + if (s->demuxer == GST_ELEMENT(parent)) { + source = s; + break; + } + } + + if (source) + nextSamplePts = source->nextSamplePts; + GST_OBJECT_UNLOCK(src); + } + + if (nextSamplePts.isInvalid()) + nextSamplePts = MediaTime::createWithDouble(0); + + // Insert a [nextSamplePts, Inf] segment to accomodate the new data + // and prevent a spureous [0, 0] segment to be generated by the demuxer. + GstSegment* segment = gst_segment_new(); + gst_segment_init(segment, GST_FORMAT_TIME); + + segment->start = toGstClockTime(nextSamplePts.toFloat()); + segment->stop = GST_CLOCK_TIME_NONE; + + gst_element_send_event(GST_ELEMENT(parent), gst_event_new_segment(segment)); + gst_segment_free(segment); + break; + } + + default: + result = gst_pad_event_default(pad, parent, event); + break; + } + + return result; +} + +static gboolean webKitMediaSrcDemuxerEventWithParent(GstPad* pad, GstObject* parent, GstEvent* event) +{ + gboolean result = FALSE; + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_SEEK: { + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type; + gint64 start; + GstSeekType stop_type; + gint64 stop; + + gst_event_parse_seek(event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); + + WebKitMediaSrc* src = NULL; + + if (parent) + src = WEBKIT_MEDIA_SRC(GST_ELEMENT_PARENT(GST_ELEMENT(parent))); + + if (src) { + GST_OBJECT_LOCK(src); + if (format == GST_FORMAT_TIME) { + gst_event_ref(event); + if (src->priv->seekEvent) + gst_event_unref(src->priv->seekEvent); + src->priv->seekEvent = event; + } + GST_OBJECT_UNLOCK(src); + } + + // No break, will fall back to the "default" case on purpose + } + + default: + result = gst_pad_event_default(pad, parent, event); + break; + } + + return result; +} + static GstPad* get_internal_linked_pad(GstPad* pad) { GstIterator* it; @@ -554,6 +716,17 @@ static gboolean webKitWebSrcDidReceiveSample(gpointer userdata) ReceiveSample* sample = (ReceiveSample*)userdata; if (sample->stream->parent) { + MediaTime timestampOffset(MediaTime::createWithDouble(sample->stream->parent->sourceBuffer->timestampOffset())); + + // Add a fake sample if a gap is detected before the first sample + if (sample->sample->presentationTime() >= timestampOffset && + sample->sample->presentationTime() <= timestampOffset + MediaTime::createWithDouble(0.1)) { + RefPtr fakeSample = WebCore::GStreamerMediaSample::createFakeSample( + timestampOffset, sample->sample->decodeTime(), sample->sample->presentationTime() - timestampOffset, sample->sample->presentationSize(), + sample->stream->audioTrack ? sample->stream->audioTrack->get()->id() : sample->stream->videoTrack->get()->id()); + sample->stream->parent->parent->priv->mediaSourceClient->didReceiveSample(sample->stream->parent->sourceBuffer, fakeSample); + } + sample->stream->parent->parent->priv->mediaSourceClient->didReceiveSample(sample->stream->parent->sourceBuffer, sample->sample); GST_OBJECT_LOCK(sample->stream->parent->parent); @@ -562,6 +735,7 @@ static gboolean webKitWebSrcDidReceiveSample(gpointer userdata) g_timeout_add(100, GSourceFunc(webKitMediaSrcLastSampleTimeout), sample->stream->parent); } sample->stream->parent->lastSampleTime = g_get_monotonic_time(); + sample->stream->parent->nextSamplePts = sample->sample->presentationTime() + sample->sample->duration(); GST_OBJECT_UNLOCK(sample->stream->parent->parent); } @@ -590,6 +764,8 @@ static gboolean webKitMediaSrcLastSampleTimeout(Source* source) // The timer has been cancelled result = G_SOURCE_REMOVE; } + if (callDidReceiveAllPendingSamples) + webkit_media_src_set_appending(source->parent, FALSE); GST_OBJECT_UNLOCK(source->parent); if (callDidReceiveAllPendingSamples) { @@ -629,7 +805,9 @@ static GstPadProbeReturn webKitWebSrcBufferProbe(GstPad*, GstPadProbeInfo* info, } GST_OBJECT_UNLOCK(stream->parent->parent); - return GST_PAD_PROBE_OK; + // This probe DROPS all the buffers. They will be reinserted in the + // pipeline by flushAndEnqueueNonDisplayingSamples() and enqueueSamples(). + return GST_PAD_PROBE_DROP; } static GstPadProbeReturn webKitWebSrcBufferAfterMultiqueueProbe(GstPad* pad, GstPadProbeInfo* info, Stream* stream) @@ -717,6 +895,7 @@ static gboolean webKitMediaSrcNoDataToDecodeTimeout(Source* source) { GST_OBJECT_LOCK(source->parent); source->noDataToDecodeTimeoutTag = 0; + webkit_media_src_set_appending(source->parent, FALSE); GST_OBJECT_UNLOCK(source->parent); source->parent->priv->mediaSourceClient->didReceiveAllPendingSamples(source->sourceBuffer); @@ -750,7 +929,7 @@ static void webKitMediaSrcParserNotifyCaps(GObject* object, GParamSpec*, Stream* webKitMediaSrcDemuxerNoMorePads(NULL, stream->parent); } -static void webKitMediaSrcDemuxerPadAdded(GstElement* demuxer, GstPad* demuxersrcpad, Source* source) +static void webKitMediaSrcDemuxerPadAdded(GstElement*, GstPad* demuxersrcpad, Source* source) { GstCaps* demuxersrcpadcaps = gst_pad_get_current_caps(demuxersrcpad); GstStructure* s = gst_caps_get_structure(demuxersrcpadcaps, 0); @@ -831,6 +1010,8 @@ static void webKitMediaSrcDemuxerPadAdded(GstElement* demuxer, GstPad* demuxersr g_free(parserBinName); + gst_pad_set_event_function(stream->demuxersrcpad, webKitMediaSrcDemuxerEventWithParent); + GST_OBJECT_LOCK(source->parent); source->streams = g_list_prepend(source->streams, stream); GST_OBJECT_UNLOCK(source->parent); @@ -917,7 +1098,7 @@ static gboolean freeSourceLater(Source* source) return G_SOURCE_REMOVE; } -static void webKitMediaSrcDemuxerPadRemoved(GstElement* demuxer, GstPad* demuxersrcpad, Source* source) +static void webKitMediaSrcDemuxerPadRemoved(GstElement*, GstPad* demuxersrcpad, Source* source) { // Locate the right stream Stream* stream = 0; @@ -994,6 +1175,8 @@ static gboolean webKitMediaSrcDidReceiveInitializationSegment(gpointer userdata) break; } } + if (noData) + webkit_media_src_set_appending(source->parent, FALSE); GST_OBJECT_UNLOCK(source->parent); if (noData) { @@ -1035,10 +1218,23 @@ static gboolean webKitMediaSrcDidReceiveInitializationSegment(gpointer userdata) Stream* stream = (Stream*)l->data; if (stream->initSegmentAlreadyProcessed) continue; + MediaTime timestampOffset(MediaTime::createWithDouble(source->sourceBuffer->timestampOffset())); + GList* m; for (m = stream->pendingReceiveSample; m; m = m->next) { PendingReceiveSample* pending = (PendingReceiveSample*)m->data; RefPtr sample = WebCore::GStreamerMediaSample::create(pending->buffer, pending->presentationSize, stream->audioTrack ? stream->audioTrack->get()->id() : stream->videoTrack->get()->id()); + + // Add a fake sample if a gap is detected before the first sample + if (samples.size()==0 && + sample->presentationTime() >= timestampOffset && + sample->presentationTime() <= timestampOffset + MediaTime::createWithDouble(0.1)) { + RefPtr fakeSample = WebCore::GStreamerMediaSample::createFakeSample( + timestampOffset, sample->decodeTime(), sample->presentationTime() - timestampOffset, pending->presentationSize, + stream->audioTrack ? stream->audioTrack->get()->id() : stream->videoTrack->get()->id()); + samples.append(fakeSample); + } + samples.append(sample); gst_buffer_unref(pending->buffer); g_free(pending); @@ -1049,12 +1245,17 @@ static gboolean webKitMediaSrcDidReceiveInitializationSegment(gpointer userdata) } GST_OBJECT_UNLOCK(source->parent); + MediaTime nextSamplePts = MediaTime::invalidTime(); for (Vector >::iterator it = samples.begin(); it != samples.end(); ++it) { RefPtr sample = *it; source->parent->priv->mediaSourceClient->didReceiveSample(source->sourceBuffer, sample); + nextSamplePts = sample->presentationTime() + sample->duration(); } GST_OBJECT_LOCK(source->parent); + if (nextSamplePts.isValid()) + source->nextSamplePts = nextSamplePts; + // The timeout on this timestamp is what helps the append operation to be completed if (!source->lastSampleTime) { g_timeout_add(100, GSourceFunc(webKitMediaSrcLastSampleTimeout), source); @@ -1091,7 +1292,7 @@ static void webKitMediaSrcDemuxerNoMorePads(GstElement*, Source* source) } } -static void webKitMediaSrcHaveType(GstElement* typefind, guint probability, GstCaps* caps, Source* source) +static void webKitMediaSrcHaveType(GstElement* typefind, guint, GstCaps* caps, Source* source) { GST_OBJECT_LOCK(source->parent); bool alreadyProcessed = source->demuxer || source->streams; @@ -1150,6 +1351,10 @@ static void webKitMediaSrcHaveType(GstElement* typefind, guint probability, GstC gst_element_link_pads(typefind, "src", source->demuxer, "sink"); + GstPad* demuxersinkpad = gst_element_get_static_pad(source->demuxer, "sink"); + gst_pad_set_event_function(demuxersinkpad, webKitMediaSrcDemuxerSinkEventWithParent); + gst_object_unref(demuxersinkpad); + g_signal_connect(demuxer, "pad-added", G_CALLBACK(webKitMediaSrcDemuxerPadAdded), source); g_signal_connect(demuxer, "pad-removed", G_CALLBACK(webKitMediaSrcDemuxerPadRemoved), source); g_signal_connect(demuxer, "no-more-pads", G_CALLBACK(webKitMediaSrcDemuxerNoMorePads), source); @@ -1216,6 +1421,59 @@ static void webKitMediaSrcUriHandlerInit(gpointer gIface, gpointer) iface->set_uri = webKitMediaSrcSetUri; } +static gboolean webKitMediaSrcSeekDataCb(GstAppSrc*, guint64 offset, gpointer userData) +{ + WebKitMediaSrc* src = WEBKIT_MEDIA_SRC(userData); + WebKitMediaSrcPrivate* priv = src->priv; + bool result; + + GST_DEBUG_OBJECT(src, "Seeking to offset: %" G_GUINT64_FORMAT, offset); + + GST_OBJECT_LOCK(src); + if (offset == priv->offset && priv->requestedOffset == priv->offset) + result = TRUE; + else if (!priv->mediaPlayerPrivate || priv->mediaPlayerPrivate->isLiveStream()) + result = FALSE; + else { + GST_DEBUG_OBJECT(src, "Doing range-request seek"); + priv->requestedOffset = offset; + result = TRUE; + } + GST_OBJECT_UNLOCK(src); + + return result; +} + +static Stream* getStreamByTrackId(WebKitMediaSrc* src, AtomicString trackIDString) +{ + // WebKitMediaSrc should be locked at this point. + for (GList* sources = src->priv->sources; sources; sources = sources->next) { + Source* source = static_cast(sources->data); + for (GList* streams = source->streams; streams; streams = streams->next) { + Stream* stream = static_cast(streams->data); + const AtomicString& id = stream->audioTrack ? stream->audioTrack->get()->id() : stream->videoTrack->get()->id(); + if (id == trackIDString) + return stream; + } + } + return NULL; +} + +static Stream* getStreamByDemuxerPad(WebKitMediaSrc* src, const GstPad* demuxersrcpad) +{ + // WebKitMediaSrc should be locked at this point. + for (GList* sources = src->priv->sources; sources; sources = sources->next) { + Source* source = static_cast(sources->data); + for (GList* streams = source->streams; streams; streams = streams->next) { + Stream* stream = static_cast(streams->data); + if (stream->demuxersrcpad == demuxersrcpad) + return stream; + } + } + + return NULL; +} + namespace WebCore { PassRefPtr MediaSourceClientGStreamer::create(WebKitMediaSrc* src) { @@ -1256,6 +1514,10 @@ MediaSourcePrivate::AddStatus MediaSourceClientGStreamer::addSourceBuffer(PassRe source->typefind = gst_element_factory_make("typefind", typefindName.get()); source->noDataToDecodeTimeoutTag = 0; + gst_app_src_set_callbacks(GST_APP_SRC(source->src), &appsrcCallbacks, source->parent, 0); + gst_app_src_set_emit_signals(GST_APP_SRC(source->src), FALSE); + gst_app_src_set_stream_type(GST_APP_SRC(source->src), GST_APP_STREAM_TYPE_SEEKABLE); + g_signal_connect(source->typefind, "have-type", G_CALLBACK(webKitMediaSrcHaveType), source); source->sourceBuffer = sourceBufferPrivate.get(); @@ -1321,6 +1583,8 @@ bool MediaSourceClientGStreamer::append(PassRefPtr stream->initSegmentAlreadyProcessed = false; } } + if (source && source->src) + webkit_media_src_set_appending(m_src.get(), TRUE); GST_OBJECT_UNLOCK(m_src.get()); if (!source || !source->src) @@ -1350,10 +1614,16 @@ bool MediaSourceClientGStreamer::append(PassRefPtr ret = gst_app_src_push_buffer(GST_APP_SRC(source->src), buffer); - return (ret == GST_FLOW_OK); + bool ok = (ret == GST_FLOW_OK); + if (!ok) { + GST_OBJECT_LOCK(m_src.get()); + webkit_media_src_set_appending(m_src.get(), FALSE); + GST_OBJECT_UNLOCK(m_src.get()); + } + return ok; } -void MediaSourceClientGStreamer::markEndOfStream(MediaSourcePrivate::EndOfStreamStatus status) +void MediaSourceClientGStreamer::markEndOfStream(MediaSourcePrivate::EndOfStreamStatus) { WebKitMediaSrcPrivate* priv = m_src->priv; GList *l; @@ -1431,6 +1701,94 @@ void MediaSourceClientGStreamer::removedFromMediaSource(PassRefPtr > samples, AtomicString trackIDString) +{ + GST_OBJECT_LOCK(m_src.get()); + Stream* stream = getStreamByTrackId(m_src.get(), trackIDString); + + if (!stream) { + GST_OBJECT_UNLOCK(m_src.get()); + return; + } + + GstPad* demuxersrcpad = stream->demuxersrcpad; + MediaTime seekTime = stream->parent->parent->priv->seekTime; + GstEvent* seekEvent = stream->parent->parent->priv->seekEvent; + GST_OBJECT_UNLOCK(m_src.get()); + + GstSegment* segment = NULL; + + if (seekTime.isInvalid() || !seekEvent) + return; + + gdouble rate; + GstFormat format; + GstSeekFlags flags; + GstSeekType start_type; + gint64 start; + GstSeekType stop_type; + gint64 stop; + gboolean update = false; + + gst_event_parse_seek(seekEvent, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); + + segment = gst_segment_new(); + segment->format = format; + + gst_segment_do_seek(segment, rate, format, flags, start_type, start, stop_type, stop, &update); + + if (!demuxersrcpad) { + if (segment) + gst_segment_free(segment); + return; + } + + // QtWebKit needs this line, but here it causes a delay of seekTime seconds. + // gst_pad_set_offset(demuxersrcpad, toGstClockTime(seekTime.toDouble())); + + gst_pad_push_event(demuxersrcpad, gst_event_new_segment(segment)); + gst_segment_free(segment); + + GST_OBJECT_LOCK(m_src.get()); + WebKitMediaSrc* src = m_src.get(); + src->priv->flushAndReenqueueCount++; + GST_OBJECT_UNLOCK(m_src.get()); + + GstPad* multiqueuesinkpad = gst_pad_get_peer(demuxersrcpad); + for (Vector >::iterator it = samples.begin(); it != samples.end(); ++it) { + GStreamerMediaSample* sample = static_cast(it->get()); + if (sample->buffer()) { + GstBuffer* buffer = gst_buffer_ref(sample->buffer()); + GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_FLAG_DECODE_ONLY); + gst_pad_chain(multiqueuesinkpad, buffer); + } + } + gst_object_unref(multiqueuesinkpad); +} + +void MediaSourceClientGStreamer::enqueueSample(PassRefPtr prsample, AtomicString trackIDString) +{ + GST_OBJECT_LOCK(m_src.get()); + Stream* stream = getStreamByTrackId(m_src.get(), trackIDString); + + if (!stream) { + GST_OBJECT_UNLOCK(m_src.get()); + return; + } + + GstPad* demuxersrcpad = stream->demuxersrcpad; + GST_OBJECT_UNLOCK(m_src.get()); + + GstPad* multiqueuesinkpad = gst_pad_get_peer(demuxersrcpad); + RefPtr rsample(prsample); + GStreamerMediaSample* sample = static_cast(rsample.get()); + if (sample->buffer()) { + GstBuffer* buffer = gst_buffer_ref(sample->buffer()); + gst_pad_chain(multiqueuesinkpad, buffer); + } + gst_object_unref(multiqueuesinkpad); +} + #if ENABLE(VIDEO_TRACK) void MediaSourceClientGStreamer::didReceiveInitializationSegment(SourceBufferPrivateGStreamer* sourceBuffer, const SourceBufferPrivateClient::InitializationSegment& initializationSegment) { @@ -1525,28 +1883,144 @@ GstPad* webkit_media_src_get_text_pad(WebKitMediaSrc* src, guint i) return result; } -// Pad NUST be the WebKitMediaSrc demuxer pad (aka: stream->demuxersrcpad) associated with the added track +void webkit_media_src_set_mediaplayerprivate(WebKitMediaSrc* src, WebCore::MediaPlayerPrivateGStreamer* mediaPlayerPrivate) +{ + GST_OBJECT_LOCK(src); + // Set to 0 on MediaPlayerPrivateGStreamer destruction, never a dangling pointer + src->priv->mediaPlayerPrivate = mediaPlayerPrivate; + GST_OBJECT_UNLOCK(src); +} + +// Pad MUST be the WebKitMediaSrc demuxer pad (aka: stream->demuxersrcpad) associated with the added track void webkit_media_src_track_added(WebKitMediaSrc* src, GstPad* pad, GstEvent* event) { // Find the stream->srcpad (aka: ghostpad) associated with the provided demuxersrcpad GST_OBJECT_LOCK(src); GstPad* srcpad = NULL; - for (GList* sources = src->priv->sources; sources && !srcpad; sources = sources->next) { - Source* source = (Source*)sources->data; - for (GList* streams = source->streams; streams; streams = streams->next) { - Stream* stream = (Stream*)streams->data; - if (stream->demuxersrcpad == pad) { - srcpad = stream->srcpad; - break; + Stream* stream = getStreamByDemuxerPad(src, pad); + + if (stream) + srcpad = stream->srcpad; + GST_OBJECT_UNLOCK(src); + + ASSERT(srcpad); + + webKitMediaSrcEventWithParent(srcpad, NULL, event); +} + +void webkit_media_src_set_seek_time(WebKitMediaSrc* src, const MediaTime& time) +{ + src->priv->seekTime = time; + src->priv->flushAndReenqueueCount = 0; +} + +static GstClockTime toGstClockTime(float time) +{ + // Extract the integer part of the time (seconds) and the fractional part (microseconds). Attempt to + // round the microseconds so no floating point precision is lost and we can perform an accurate seek. + float seconds; + float microSeconds = std::modf(time, &seconds) * 1000000; + GTimeVal timeValue; + timeValue.tv_sec = static_cast(seconds); + timeValue.tv_usec = static_cast(roundf(microSeconds / 10000) * 10000); + return GST_TIMEVAL_TO_TIME(timeValue); +} + +void webkit_media_src_segment_needed(WebKitMediaSrc* src, StreamType streamType) +{ + // The video sink has received reset-time and needs a new segment before + // new frames can be pushed. The new segment will be pushed to the + // multiqueue video srcpad + GST_OBJECT_LOCK(src); + MediaTime seekTime = src->priv->seekTime; + int flushAndReenqueueCount = src->priv->flushAndReenqueueCount; + + if (seekTime.isValid() && flushAndReenqueueCount > 0) { + src->priv->flushAndReenqueueCount--; + + if (src->priv->flushAndReenqueueCount == 0) { + if (src->priv->seekTime.isValid()) + src->priv->seekTime = MediaTime::invalidTime(); + + GstEvent* seekEvent = src->priv->seekEvent; + if (seekEvent) { + src->priv->seekEvent = NULL; + gst_event_unref(seekEvent); } } } GST_OBJECT_UNLOCK(src); - ASSERT(srcpad); + if (seekTime.isValid()) { + // The flushAndReenqueue method will take care of pushing the segment + if (flushAndReenqueueCount > 0) + return; - webKitMediaSrcEventWithParent(srcpad, NULL, event); + GstPad* demuxersrcpad = NULL; + + switch (streamType) { + case STREAM_TYPE_AUDIO: + demuxersrcpad = webkit_media_src_get_audio_pad(src, 0); + break; + case STREAM_TYPE_VIDEO: + demuxersrcpad = webkit_media_src_get_video_pad(src, 0); + break; + default: + break; + } + + if (!demuxersrcpad) + return; + + GstSegment* segment = gst_segment_new(); + + gst_segment_init(segment, GST_FORMAT_TIME); + segment->start = toGstClockTime(seekTime.toFloat()); + segment->stop = GST_CLOCK_TIME_NONE; + + gst_pad_push_event(demuxersrcpad, gst_event_new_segment(segment)); + gst_segment_free(segment); + } +} + +gboolean webkit_media_src_is_appending(WebKitMediaSrc* src) +{ + gboolean isAppending = FALSE; + + GST_OBJECT_LOCK(src); + if (src->priv) + isAppending = src->priv->isAppending; + GST_OBJECT_UNLOCK(src); + + return isAppending; +} + +static gboolean webKitMediaSrcNotifyAppendCompleteToPlayer(WebKitMediaSrc* src) +{ + WebCore::MediaPlayerPrivateGStreamer* mediaPlayerPrivate = 0; + + GST_OBJECT_LOCK(src); + mediaPlayerPrivate = src->priv->mediaPlayerPrivate; + GST_OBJECT_UNLOCK(src); + + if (mediaPlayerPrivate) + mediaPlayerPrivate->notifyAppendComplete(); + + gst_object_unref(src); + return G_SOURCE_REMOVE; +} + +static void webkit_media_src_set_appending(WebKitMediaSrc* src, gboolean isAppending) +{ + // WebKitMediaSrc should be locked at this point. + if (src->priv) { + gboolean wasAppending = src->priv->isAppending; + src->priv->isAppending = isAppending; + + if (wasAppending && !isAppending) + g_timeout_add(0, GSourceFunc(webKitMediaSrcNotifyAppendCompleteToPlayer), gst_object_ref(src)); + } } namespace WTF { diff --git a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h b/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h index bb42fe157225f..fe0eb6fcfae90 100644 --- a/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h +++ b/Source/WebCore/platform/graphics/gstreamer/WebKitMediaSourceGStreamer.h @@ -27,6 +27,7 @@ #include "MediaSource.h" #include "SourceBufferPrivateClient.h" #include "SourceBufferPrivate.h" +#include "MediaPlayerPrivateGStreamer.h" #include "GRefPtrGStreamer.h" @@ -40,6 +41,8 @@ G_BEGIN_DECLS #define WEBKIT_IS_MEDIA_SRC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WEBKIT_TYPE_MEDIA_SRC)) #define WEBKIT_IS_MEDIA_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), WEBKIT_TYPE_MEDIA_SRC)) +typedef enum _StreamType {STREAM_TYPE_UNKNOWN, STREAM_TYPE_AUDIO, STREAM_TYPE_VIDEO, STREAM_TYPE_TEXT} StreamType; + typedef struct _WebKitMediaSrc WebKitMediaSrc; typedef struct _WebKitMediaSrcClass WebKitMediaSrcClass; typedef struct _WebKitMediaSrcPrivate WebKitMediaSrcPrivate; @@ -64,8 +67,12 @@ GType webkit_media_src_get_type(void); GstPad* webkit_media_src_get_audio_pad(WebKitMediaSrc* src, guint i); GstPad* webkit_media_src_get_video_pad(WebKitMediaSrc* src, guint i); GstPad* webkit_media_src_get_text_pad(WebKitMediaSrc* src, guint i); +void webkit_media_src_set_mediaplayerprivate(WebKitMediaSrc* src, WebCore::MediaPlayerPrivateGStreamer* player); void webkit_media_src_track_added(WebKitMediaSrc*, GstPad* pad, GstEvent* event); +void webkit_media_src_set_seek_time(WebKitMediaSrc*, const MediaTime&); +void webkit_media_src_segment_needed(WebKitMediaSrc*, StreamType); +gboolean webkit_media_src_is_appending(WebKitMediaSrc*); G_END_DECLS @@ -94,6 +101,8 @@ class MediaSourceClientGStreamer: public RefCounted // From SourceBufferPrivateGStreamer bool append(PassRefPtr, const unsigned char*, unsigned); void removedFromMediaSource(PassRefPtr); + void flushAndEnqueueNonDisplayingSamples(Vector > samples, AtomicString trackIDString); + void enqueueSample(PassRefPtr sample, AtomicString trackIDString); // From our WebKitMediaSrc #if ENABLE(VIDEO_TRACK)