1/*
2 * Copyright (C) 2014, 2015 Sebastian Dröge <sebastian@centricular.com>
3 * Copyright (C) 2016 Metrological Group B.V.
4 * Copyright (C) 2016 Igalia S.L
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * aint with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22#include "config.h"
23#include "PlaybackPipeline.h"
24
25#if ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(MEDIA_SOURCE)
26
27#include "AudioTrackPrivateGStreamer.h"
28#include "GStreamerMediaSample.h"
29#include "GStreamerUtilities.h"
30#include "MediaSample.h"
31#include "SourceBufferPrivateGStreamer.h"
32#include "VideoTrackPrivateGStreamer.h"
33
34#include <gst/app/gstappsrc.h>
35#include <gst/gst.h>
36#include <wtf/MainThread.h>
37#include <wtf/RefCounted.h>
38#include <wtf/glib/GMutexLocker.h>
39#include <wtf/glib/GRefPtr.h>
40#include <wtf/glib/GUniquePtr.h>
41#include <wtf/text/AtomicString.h>
42
43static Stream* getStreamByTrackId(WebKitMediaSrc*, AtomicString);
44static Stream* getStreamBySourceBufferPrivate(WebKitMediaSrc*, WebCore::SourceBufferPrivateGStreamer*);
45
46static Stream* getStreamByTrackId(WebKitMediaSrc* source, AtomicString trackIdString)
47{
48 // WebKitMediaSrc should be locked at this point.
49 for (Stream* stream : source->priv->streams) {
50 if (stream->type != WebCore::Invalid
51 && ((stream->audioTrack && stream->audioTrack->id() == trackIdString)
52 || (stream->videoTrack && stream->videoTrack->id() == trackIdString) ) ) {
53 return stream;
54 }
55 }
56 return nullptr;
57}
58
59static Stream* getStreamBySourceBufferPrivate(WebKitMediaSrc* source, WebCore::SourceBufferPrivateGStreamer* sourceBufferPrivate)
60{
61 for (Stream* stream : source->priv->streams) {
62 if (stream->sourceBuffer == sourceBufferPrivate)
63 return stream;
64 }
65 return nullptr;
66}
67
68// FIXME: Use gst_app_src_push_sample() instead when we switch to the appropriate GStreamer version.
69static GstFlowReturn pushSample(GstAppSrc* appsrc, GstSample* sample)
70{
71 g_return_val_if_fail(GST_IS_SAMPLE(sample), GST_FLOW_ERROR);
72
73 GstCaps* caps = gst_sample_get_caps(sample);
74 if (caps)
75 gst_app_src_set_caps(appsrc, caps);
76 else
77 GST_WARNING_OBJECT(appsrc, "received sample without caps");
78
79 GstBuffer* buffer = gst_sample_get_buffer(sample);
80 if (UNLIKELY(!buffer)) {
81 GST_WARNING_OBJECT(appsrc, "received sample without buffer");
82 return GST_FLOW_OK;
83 }
84
85 // gst_app_src_push_buffer() steals the reference, we need an additional one.
86 return gst_app_src_push_buffer(appsrc, gst_buffer_ref(buffer));
87}
88
89namespace WebCore {
90
91PassRefPtr<PlaybackPipeline> PlaybackPipeline::create()
92{
93 return adoptRef(new PlaybackPipeline());
94}
95
96PlaybackPipeline::PlaybackPipeline()
97 : RefCounted<PlaybackPipeline>()
98{
99}
100
101PlaybackPipeline::~PlaybackPipeline()
102{
103}
104
105void PlaybackPipeline::setWebKitMediaSrc(WebKitMediaSrc* webKitMediaSrc)
106{
107 GST_DEBUG("webKitMediaSrc=%p", webKitMediaSrc);
108 m_webKitMediaSrc = webKitMediaSrc;
109}
110
111WebKitMediaSrc* PlaybackPipeline::webKitMediaSrc()
112{
113 return m_webKitMediaSrc.get();
114}
115
116MediaSourcePrivate::AddStatus PlaybackPipeline::addSourceBuffer(RefPtr<SourceBufferPrivateGStreamer> sourceBufferPrivate)
117{
118 WebKitMediaSrcPrivate* priv = m_webKitMediaSrc->priv;
119
120 if (priv->allTracksConfigured) {
121 GST_ERROR_OBJECT(m_webKitMediaSrc.get(), "Adding new source buffers after first data not supported yet");
122 return MediaSourcePrivate::NotSupported;
123 }
124
125 GST_DEBUG_OBJECT(m_webKitMediaSrc.get(), "State %d", int(GST_STATE(m_webKitMediaSrc.get())));
126
127 Stream* stream = new Stream { 0 };
128 stream->parent = m_webKitMediaSrc.get();
129 stream->appsrc = gst_element_factory_make("appsrc", nullptr);
130 stream->appsrcNeedDataFlag = false;
131 stream->sourceBuffer = sourceBufferPrivate.get();
132
133 // No track has been attached yet.
134 stream->type = Invalid;
135 stream->parser = nullptr;
136 stream->caps = nullptr;
137#if ENABLE(VIDEO_TRACK)
138 stream->audioTrack = nullptr;
139 stream->videoTrack = nullptr;
140#endif
141 stream->presentationSize = WebCore::FloatSize();
142 stream->lastEnqueuedTime = MediaTime::invalidTime();
143
144 gst_app_src_set_callbacks(GST_APP_SRC(stream->appsrc), &enabledAppsrcCallbacks, stream->parent, nullptr);
145 gst_app_src_set_emit_signals(GST_APP_SRC(stream->appsrc), FALSE);
146 gst_app_src_set_stream_type(GST_APP_SRC(stream->appsrc), GST_APP_STREAM_TYPE_SEEKABLE);
147
148 gst_app_src_set_max_bytes(GST_APP_SRC(stream->appsrc), 2 * WTF::MB);
149 g_object_set(G_OBJECT(stream->appsrc), "block", FALSE, "min-percent", 20, nullptr);
150
151 GST_OBJECT_LOCK(m_webKitMediaSrc.get());
152 priv->streams.prepend(stream);
153 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
154
155 gst_bin_add(GST_BIN(m_webKitMediaSrc.get()), stream->appsrc);
156 gst_element_sync_state_with_parent(stream->appsrc);
157
158 return MediaSourcePrivate::Ok;
159}
160
161void PlaybackPipeline::removeSourceBuffer(RefPtr<SourceBufferPrivateGStreamer> sourceBufferPrivate)
162{
163 ASSERT(WTF::isMainThread());
164
165 GST_DEBUG_OBJECT(m_webKitMediaSrc.get(), "Element removed from MediaSource");
166 GST_OBJECT_LOCK(m_webKitMediaSrc.get());
167 WebKitMediaSrcPrivate* priv = m_webKitMediaSrc->priv;
168 Stream* stream = nullptr;
169 Deque<Stream*>::iterator streamPosition = priv->streams.begin();
170
171 for (; streamPosition != priv->streams.end(); ++streamPosition) {
172 if ((*streamPosition)->sourceBuffer == sourceBufferPrivate.get()) {
173 stream = *streamPosition;
174 break;
175 }
176 }
177 if (stream)
178 priv->streams.remove(streamPosition);
179 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
180
181 if (stream)
182 webKitMediaSrcFreeStream(m_webKitMediaSrc.get(), stream);
183}
184
185void PlaybackPipeline::attachTrack(RefPtr<SourceBufferPrivateGStreamer> sourceBufferPrivate, RefPtr<TrackPrivateBase> trackPrivate, GstStructure* structure, GstCaps* caps)
186{
187 WebKitMediaSrc* webKitMediaSrc = m_webKitMediaSrc.get();
188
189 GST_OBJECT_LOCK(webKitMediaSrc);
190 Stream* stream = getStreamBySourceBufferPrivate(webKitMediaSrc, sourceBufferPrivate.get());
191 GST_OBJECT_UNLOCK(webKitMediaSrc);
192
193 ASSERT(stream);
194
195 GST_OBJECT_LOCK(webKitMediaSrc);
196 unsigned padId = stream->parent->priv->numberOfPads;
197 stream->parent->priv->numberOfPads++;
198 GST_OBJECT_UNLOCK(webKitMediaSrc);
199
200 const gchar* mediaType = gst_structure_get_name(structure);
201
202 GST_DEBUG_OBJECT(webKitMediaSrc, "Configured track %s: appsrc=%s, padId=%u, mediaType=%s", trackPrivate->id().string().utf8().data(), GST_ELEMENT_NAME(stream->appsrc), padId, mediaType);
203
204 GUniquePtr<gchar> parserBinName(g_strdup_printf("streamparser%u", padId));
205
206 if (!g_strcmp0(mediaType, "video/x-h264")) {
207 GRefPtr<GstCaps> filterCaps = adoptGRef(gst_caps_new_simple("video/x-h264", "alignment", G_TYPE_STRING, "au", nullptr));
208 GstElement* capsfilter = gst_element_factory_make("capsfilter", nullptr);
209 g_object_set(capsfilter, "caps", filterCaps.get(), nullptr);
210
211 stream->parser = gst_bin_new(parserBinName.get());
212
213 GstElement* parser = gst_element_factory_make("h264parse", nullptr);
214 gst_bin_add_many(GST_BIN(stream->parser), parser, capsfilter, nullptr);
215 gst_element_link_pads(parser, "src", capsfilter, "sink");
216
217 GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(parser, "sink"));
218 gst_element_add_pad(stream->parser, gst_ghost_pad_new("sink", pad.get()));
219
220 pad = adoptGRef(gst_element_get_static_pad(capsfilter, "src"));
221 gst_element_add_pad(stream->parser, gst_ghost_pad_new("src", pad.get()));
222 } else if (!g_strcmp0(mediaType, "video/x-h265")) {
223 GRefPtr<GstCaps> filterCaps = adoptGRef(gst_caps_new_simple("video/x-h265", "alignment", G_TYPE_STRING, "au", nullptr));
224 GstElement* capsfilter = gst_element_factory_make("capsfilter", nullptr);
225 g_object_set(capsfilter, "caps", filterCaps.get(), nullptr);
226
227 stream->parser = gst_bin_new(parserBinName.get());
228
229 GstElement* parser = gst_element_factory_make("h265parse", nullptr);
230 gst_bin_add_many(GST_BIN(stream->parser), parser, capsfilter, nullptr);
231 gst_element_link_pads(parser, "src", capsfilter, "sink");
232
233 GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(parser, "sink"));
234 gst_element_add_pad(stream->parser, gst_ghost_pad_new("sink", pad.get()));
235
236 pad = adoptGRef(gst_element_get_static_pad(capsfilter, "src"));
237 gst_element_add_pad(stream->parser, gst_ghost_pad_new("src", pad.get()));
238 } else if (!g_strcmp0(mediaType, "audio/mpeg")) {
239 gint mpegversion = -1;
240 gst_structure_get_int(structure, "mpegversion", &mpegversion);
241
242 GstElement* parser;
243 if (mpegversion == 1)
244 parser = gst_element_factory_make("mpegaudioparse", nullptr);
245 else if (mpegversion == 2 || mpegversion == 4)
246 parser = gst_element_factory_make("aacparse", nullptr);
247 else
248 ASSERT_NOT_REACHED();
249
250 stream->parser = gst_bin_new(parserBinName.get());
251 gst_bin_add(GST_BIN(stream->parser), parser);
252
253 GRefPtr<GstPad> pad = adoptGRef(gst_element_get_static_pad(parser, "sink"));
254 gst_element_add_pad(stream->parser, gst_ghost_pad_new("sink", pad.get()));
255
256 pad = adoptGRef(gst_element_get_static_pad(parser, "src"));
257 gst_element_add_pad(stream->parser, gst_ghost_pad_new("src", pad.get()));
258 } else if (!g_strcmp0(mediaType, "video/x-vp9"))
259 stream->parser = nullptr;
260 else {
261 GST_ERROR_OBJECT(stream->parent, "Unsupported media format: %s", mediaType);
262 return;
263 }
264
265 GST_OBJECT_LOCK(webKitMediaSrc);
266 stream->type = Unknown;
267 GST_OBJECT_UNLOCK(webKitMediaSrc);
268
269 GRefPtr<GstPad> sourcePad;
270 if (stream->parser) {
271 gst_bin_add(GST_BIN(stream->parent), stream->parser);
272 gst_element_sync_state_with_parent(stream->parser);
273
274 GRefPtr<GstPad> sinkPad = adoptGRef(gst_element_get_static_pad(stream->parser, "sink"));
275 sourcePad = adoptGRef(gst_element_get_static_pad(stream->appsrc, "src"));
276 gst_pad_link(sourcePad.get(), sinkPad.get());
277 sourcePad = adoptGRef(gst_element_get_static_pad(stream->parser, "src"));
278 } else {
279 GST_DEBUG_OBJECT(m_webKitMediaSrc.get(), "Stream of type %s doesn't require a parser bin", mediaType);
280 sourcePad = adoptGRef(gst_element_get_static_pad(stream->appsrc, "src"));
281 }
282 ASSERT(sourcePad);
283
284 // FIXME: Is padId the best way to identify the Stream? What about trackId?
285 g_object_set_data(G_OBJECT(sourcePad.get()), "padId", GINT_TO_POINTER(padId));
286 webKitMediaSrcLinkParser(sourcePad.get(), caps, stream);
287
288 ASSERT(stream->parent->priv->mediaPlayerPrivate);
289 int signal = -1;
290 if (g_str_has_prefix(mediaType, "audio")) {
291 GST_OBJECT_LOCK(webKitMediaSrc);
292 stream->type = Audio;
293 stream->parent->priv->numberOfAudioStreams++;
294 GST_OBJECT_UNLOCK(webKitMediaSrc);
295 signal = SIGNAL_AUDIO_CHANGED;
296
297 stream->audioTrack = RefPtr<WebCore::AudioTrackPrivateGStreamer>(static_cast<WebCore::AudioTrackPrivateGStreamer*>(trackPrivate.get()));
298 } else if (g_str_has_prefix(mediaType, "video")) {
299 GST_OBJECT_LOCK(webKitMediaSrc);
300 stream->type = Video;
301 stream->parent->priv->numberOfVideoStreams++;
302 GST_OBJECT_UNLOCK(webKitMediaSrc);
303 signal = SIGNAL_VIDEO_CHANGED;
304
305 stream->videoTrack = RefPtr<WebCore::VideoTrackPrivateGStreamer>(static_cast<WebCore::VideoTrackPrivateGStreamer*>(trackPrivate.get()));
306 } else if (g_str_has_prefix(mediaType, "text")) {
307 GST_OBJECT_LOCK(webKitMediaSrc);
308 stream->type = Text;
309 stream->parent->priv->numberOfTextStreams++;
310 GST_OBJECT_UNLOCK(webKitMediaSrc);
311 signal = SIGNAL_TEXT_CHANGED;
312
313 // FIXME: Support text tracks.
314 }
315
316 if (signal != -1)
317 g_signal_emit(G_OBJECT(stream->parent), webKitMediaSrcSignals[signal], 0, nullptr);
318}
319
320void PlaybackPipeline::reattachTrack(RefPtr<SourceBufferPrivateGStreamer> sourceBufferPrivate, RefPtr<TrackPrivateBase> trackPrivate)
321{
322 GST_DEBUG("Re-attaching track");
323
324 // FIXME: Maybe remove this method. Now the caps change is managed by gst_appsrc_push_sample() in enqueueSample()
325 // and flushAndEnqueueNonDisplayingSamples().
326
327 WebKitMediaSrc* webKitMediaSrc = m_webKitMediaSrc.get();
328
329 GST_OBJECT_LOCK(webKitMediaSrc);
330 Stream* stream = getStreamBySourceBufferPrivate(webKitMediaSrc, sourceBufferPrivate.get());
331 GST_OBJECT_UNLOCK(webKitMediaSrc);
332
333 ASSERT(stream && stream->type != Invalid);
334
335 GRefPtr<GstCaps> oldAppsrcCaps = adoptGRef(gst_app_src_get_caps(GST_APP_SRC(stream->appsrc)));
336 // The caps change is managed by gst_appsrc_push_sample() in enqueueSample() and
337 // flushAndEnqueueNonDisplayingSamples(), so the caps aren't set from here.
338 GRefPtr<GstCaps> appsrcCaps = adoptGRef(gst_app_src_get_caps(GST_APP_SRC(stream->appsrc)));
339 const gchar* mediaType = gst_structure_get_name(gst_caps_get_structure(appsrcCaps.get(), 0));
340
341 if (!gst_caps_is_equal(oldAppsrcCaps.get(), appsrcCaps.get())) {
342 GST_DEBUG("Caps have changed, but reconstructing the sequence of elements is not supported yet");
343
344#ifndef GST_DISABLE_GST_DEBUG
345 GUniquePtr<gchar> stroldcaps(gst_caps_to_string(oldAppsrcCaps.get()));
346 GUniquePtr<gchar> strnewcaps(gst_caps_to_string(appsrcCaps.get()));
347 GST_DEBUG("oldcaps: %s", stroldcaps.get());
348 GST_DEBUG("newcaps: %s", strnewcaps.get());
349#endif
350 }
351
352 int signal = -1;
353
354 GST_OBJECT_LOCK(webKitMediaSrc);
355 if (g_str_has_prefix(mediaType, "audio")) {
356 ASSERT(stream->type == Audio);
357 signal = SIGNAL_AUDIO_CHANGED;
358 stream->audioTrack = RefPtr<WebCore::AudioTrackPrivateGStreamer>(static_cast<WebCore::AudioTrackPrivateGStreamer*>(trackPrivate.get()));
359 } else if (g_str_has_prefix(mediaType, "video")) {
360 ASSERT(stream->type == Video);
361 signal = SIGNAL_VIDEO_CHANGED;
362 stream->videoTrack = RefPtr<WebCore::VideoTrackPrivateGStreamer>(static_cast<WebCore::VideoTrackPrivateGStreamer*>(trackPrivate.get()));
363 } else if (g_str_has_prefix(mediaType, "text")) {
364 ASSERT(stream->type == Text);
365 signal = SIGNAL_TEXT_CHANGED;
366
367 // FIXME: Support text tracks.
368 }
369 GST_OBJECT_UNLOCK(webKitMediaSrc);
370
371 if (signal != -1)
372 g_signal_emit(G_OBJECT(stream->parent), webKitMediaSrcSignals[signal], 0, nullptr);
373}
374
375void PlaybackPipeline::notifyDurationChanged()
376{
377 gst_element_post_message(GST_ELEMENT(m_webKitMediaSrc.get()), gst_message_new_duration_changed(GST_OBJECT(m_webKitMediaSrc.get())));
378 // WebKitMediaSrc will ask MediaPlayerPrivateGStreamerMSE for the new duration later, when somebody asks for it.
379}
380
381void PlaybackPipeline::markEndOfStream(MediaSourcePrivate::EndOfStreamStatus)
382{
383 WebKitMediaSrcPrivate* priv = m_webKitMediaSrc->priv;
384
385 GST_DEBUG_OBJECT(m_webKitMediaSrc.get(), "Have EOS");
386
387 GST_OBJECT_LOCK(m_webKitMediaSrc.get());
388 bool allTracksConfigured = priv->allTracksConfigured;
389 if (!allTracksConfigured)
390 priv->allTracksConfigured = true;
391 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
392
393 if (!allTracksConfigured) {
394 gst_element_no_more_pads(GST_ELEMENT(m_webKitMediaSrc.get()));
395 webKitMediaSrcDoAsyncDone(m_webKitMediaSrc.get());
396 }
397
398 Vector<GstAppSrc*> appsrcs;
399
400 GST_OBJECT_LOCK(m_webKitMediaSrc.get());
401 for (Stream* stream : priv->streams) {
402 if (stream->appsrc)
403 appsrcs.append(GST_APP_SRC(stream->appsrc));
404 }
405 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
406
407 for (GstAppSrc* appsrc : appsrcs)
408 gst_app_src_end_of_stream(appsrc);
409}
410
411void PlaybackPipeline::flushAndEnqueueNonDisplayingSamples(Vector<RefPtr<MediaSample>> samples)
412{
413 ASSERT(WTF::isMainThread());
414
415 if (!samples.size()) {
416 GST_DEBUG("No samples, trackId unknown");
417 return;
418 }
419
420 AtomicString trackId = samples[0]->trackID();
421 GST_DEBUG("flushAndEnqueueNonDisplayingSamples: trackId=%s PTS[0]=%f ... PTS[n]=%f", trackId.string().utf8().data(), samples[0]->presentationTime().toFloat(), samples[samples.size()-1]->presentationTime().toFloat());
422
423 GST_DEBUG_OBJECT(m_webKitMediaSrc.get(), "Flushing and re-enqueing %zu samples for stream %s", samples.size(), trackId.string().utf8().data());
424
425 GST_OBJECT_LOCK(m_webKitMediaSrc.get());
426 Stream* stream = getStreamByTrackId(m_webKitMediaSrc.get(), trackId);
427
428 if (!stream) {
429 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
430 return;
431 }
432
433 if (!stream->sourceBuffer->isReadyForMoreSamples(trackId)) {
434 GST_DEBUG("flushAndEnqueueNonDisplayingSamples: skip adding new sample for trackId=%s, SB is not ready yet", trackId.string().utf8().data());
435 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
436 return;
437 }
438
439 GstElement* appsrc = stream->appsrc;
440 MediaTime lastEnqueuedTime = stream->lastEnqueuedTime;
441 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
442
443 if (!m_webKitMediaSrc->priv->mediaPlayerPrivate->seeking())
444 GST_DEBUG("flushAndEnqueueNonDisplayingSamples: trackId=%s pipeline needs flushing.", trackId.string().utf8().data());
445
446 for (const RefPtr<MediaSample>& sample : samples) {
447 GStreamerMediaSample* gStreamerMediaSample = static_cast<GStreamerMediaSample*>(sample.get());
448 GstBuffer* buffer = nullptr;
449 if (gStreamerMediaSample->sample())
450 buffer = gst_sample_get_buffer(gStreamerMediaSample->sample());
451 if (buffer) {
452 GstSample* gstSample = gst_sample_ref(gStreamerMediaSample->sample());
453 lastEnqueuedTime = gStreamerMediaSample->presentationTime();
454
455 GST_BUFFER_FLAG_SET(buffer, GST_BUFFER_FLAG_DECODE_ONLY);
456 pushSample(GST_APP_SRC(appsrc), gstSample);
457 // gst_app_src_push_sample() uses transfer-none for gstSample.
458
459 gst_sample_unref(gstSample);
460 }
461 }
462 GST_OBJECT_LOCK(m_webKitMediaSrc.get());
463 stream->lastEnqueuedTime = lastEnqueuedTime;
464 GST_OBJECT_UNLOCK(m_webKitMediaSrc.get());
465}
466
467void PlaybackPipeline::enqueueSample(PassRefPtr<MediaSample> prSample)
468{
469 ASSERT(WTF::isMainThread());
470
471 RefPtr<MediaSample> protectedPrSample = prSample;
472 AtomicString trackId = protectedPrSample->trackID();
473
474 GST_TRACE("enqueing sample trackId=%s PTS=%f presentationSize=%.0fx%.0f at %" GST_TIME_FORMAT " duration: %" GST_TIME_FORMAT,
475 trackId.string().utf8().data(), protectedPrSample->presentationTime().toFloat(),
476 protectedPrSample->presentationSize().width(), protectedPrSample->presentationSize().height(),
477 GST_TIME_ARGS(WebCore::toGstClockTime(protectedPrSample->presentationTime().toDouble())),
478 GST_TIME_ARGS(WebCore::toGstClockTime(protectedPrSample->duration().toDouble())));
479
480 Stream* stream = getStreamByTrackId(m_webKitMediaSrc.get(), trackId);
481
482 if (!stream) {
483 GST_WARNING("No stream!");
484 return;
485 }
486
487 if (!stream->sourceBuffer->isReadyForMoreSamples(trackId)) {
488 GST_DEBUG("enqueueSample: skip adding new sample for trackId=%s, SB is not ready yet", trackId.string().utf8().data());
489 return;
490 }
491
492 GstElement* appsrc = stream->appsrc;
493 MediaTime lastEnqueuedTime = stream->lastEnqueuedTime;
494
495 GStreamerMediaSample* sample = static_cast<GStreamerMediaSample*>(protectedPrSample.get());
496 if (sample->sample() && gst_sample_get_buffer(sample->sample())) {
497 GRefPtr<GstSample> gstSample = sample->sample();
498 GstBuffer* buffer = gst_sample_get_buffer(gstSample.get());
499 lastEnqueuedTime = sample->presentationTime();
500
501 GST_BUFFER_FLAG_UNSET(buffer, GST_BUFFER_FLAG_DECODE_ONLY);
502 pushSample(GST_APP_SRC(appsrc), gstSample.get());
503 // gst_app_src_push_sample() uses transfer-none for gstSample.
504
505 stream->lastEnqueuedTime = lastEnqueuedTime;
506 }
507}
508
509GstElement* PlaybackPipeline::pipeline()
510{
511 if (!m_webKitMediaSrc || !GST_ELEMENT_PARENT(GST_ELEMENT(m_webKitMediaSrc.get())))
512 return nullptr;
513
514 return GST_ELEMENT_PARENT(GST_ELEMENT_PARENT(GST_ELEMENT(m_webKitMediaSrc.get())));
515}
516
517} // namespace WebCore.
518
519#endif // USE(GSTREAMER)