Music Hub ..
A session-wide music playback service
 
Loading...
Searching...
No Matches
engine.cpp
Go to the documentation of this file.
1/*
2 * Copyright © 2013-2014 Canonical Ltd.
3 * Copyright © 2022 UBports Foundation.
4 *
5 * Contact: Alberto Mardegan <mardy@users.sourceforge.net>
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License version 3,
9 * as published by the Free Software Foundation.
10 *
11 * This program 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
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Authored by: Thomas Voß <thomas.voss@canonical.com>
20 * Jim Hodapp <jim.hodapp@canonical.com>
21 * Alfonso Sanchez-Beato <alfonso.sanchez-beato@canonical.com>
22 */
23
24#include <stdio.h>
25#include <stdlib.h>
26
27#include "bus.h"
28#include "engine.h"
29#include "logging.h"
30#include "meta_data_extractor.h"
31#include "playbin.h"
32
33#include <cassert>
34
36
37using namespace std;
38
39namespace gstreamer
40{
41struct Init
42{
44 {
45 gst_init(nullptr, nullptr);
46 }
47
49 {
50 gst_deinit();
51 }
53
55{
56 Q_DECLARE_PUBLIC(Engine)
57
58public:
60 {
61 if (state.new_state == GST_STATE_PLAYING)
62 return media::Player::PlaybackStatus::playing;
63 else if (state.new_state == GST_STATE_PAUSED)
64 return media::Player::PlaybackStatus::paused;
65 else if (state.new_state == GST_STATE_READY)
66 return media::Player::PlaybackStatus::ready;
67 else if (state.new_state == GST_STATE_NULL)
68 return media::Player::PlaybackStatus::null;
69 else
70 return media::Player::PlaybackStatus::stopped;
71 }
72
74 const QByteArray &source)
75 {
76 Q_Q(Engine);
77
78 if (source == "playbin")
79 {
80 MH_INFO("State changed on playbin: %s",
81 gst_element_state_get_name(state.new_state));
82 const auto status = gst_state_to_player_status(state);
83 /*
84 * When state moves to "paused" the pipeline is already set. We check that we
85 * have streams to play.
86 */
87 if (status == media::Player::PlaybackStatus::paused &&
89 MH_ERROR("** Cannot play: some codecs are missing");
90 playbin.reset();
91 const media::Player::Error e = media::Player::Error::format_error;
92 Q_EMIT q->errorOccurred(e);
93 } else if (status == media::Player::PlaybackStatus::paused &&
94 (q->state() == Engine::State::ready ||
95 q->state() == Engine::State::playing)) {
96 /* This is a spontaneus state change happening during the
97 * playbin initialization; we can ignore it. */
98 } else {
99 q->setPlaybackStatus(status);
100 }
101 }
102 }
103
104 // Converts from a GStreamer GError to a media::Player:Error enum
106 {
107 media::Player::Error ret_error = media::Player::Error::no_error;
108
109 if (g_strcmp0(g_quark_to_string(ewi.error->domain), "gst-core-error-quark") == 0)
110 {
111 switch (ewi.error->code)
112 {
113 case GST_CORE_ERROR_FAILED:
114 MH_ERROR("** Encountered a GST_CORE_ERROR_FAILED");
115 ret_error = media::Player::Error::resource_error;
116 break;
117 case GST_CORE_ERROR_NEGOTIATION:
118 MH_ERROR("** Encountered a GST_CORE_ERROR_NEGOTIATION");
119 ret_error = media::Player::Error::resource_error;
120 break;
121 case GST_CORE_ERROR_MISSING_PLUGIN:
122 MH_ERROR("** Encountered a GST_CORE_ERROR_MISSING_PLUGIN");
123 ret_error = media::Player::Error::format_error;
124 break;
125 default:
126 MH_ERROR("** Encountered an unhandled core error: '%s' (code: %d)",
127 ewi.debug, ewi.error->code);
128 ret_error = media::Player::Error::no_error;
129 break;
130 }
131 }
132 else if (g_strcmp0(g_quark_to_string(ewi.error->domain), "gst-resource-error-quark") == 0)
133 {
134 switch (ewi.error->code)
135 {
136 case GST_RESOURCE_ERROR_FAILED:
137 MH_ERROR("** Encountered a GST_RESOURCE_ERROR_FAILED");
138 ret_error = media::Player::Error::resource_error;
139 break;
140 case GST_RESOURCE_ERROR_NOT_FOUND:
141 MH_ERROR("** Encountered a GST_RESOURCE_ERROR_NOT_FOUND");
142 ret_error = media::Player::Error::resource_error;
143 break;
144 case GST_RESOURCE_ERROR_OPEN_READ:
145 MH_ERROR("** Encountered a GST_RESOURCE_ERROR_OPEN_READ");
146 ret_error = media::Player::Error::resource_error;
147 break;
148 case GST_RESOURCE_ERROR_OPEN_WRITE:
149 MH_ERROR("** Encountered a GST_RESOURCE_ERROR_OPEN_WRITE");
150 ret_error = media::Player::Error::resource_error;
151 break;
152 case GST_RESOURCE_ERROR_READ:
153 MH_ERROR("** Encountered a GST_RESOURCE_ERROR_READ");
154 ret_error = media::Player::Error::resource_error;
155 break;
156 case GST_RESOURCE_ERROR_WRITE:
157 MH_ERROR("** Encountered a GST_RESOURCE_ERROR_WRITE");
158 ret_error = media::Player::Error::resource_error;
159 break;
160 case GST_RESOURCE_ERROR_NOT_AUTHORIZED:
161 MH_ERROR("** Encountered a GST_RESOURCE_ERROR_NOT_AUTHORIZED");
162 ret_error = media::Player::Error::access_denied_error;
163 break;
164 default:
165 MH_ERROR("** Encountered an unhandled resource error: '%s' (code: %d)",
166 ewi.debug, ewi.error->code);
167 ret_error = media::Player::Error::no_error;
168 break;
169 }
170 }
171 else if (g_strcmp0(g_quark_to_string(ewi.error->domain), "gst-stream-error-quark") == 0)
172 {
173 switch (ewi.error->code)
174 {
175 case GST_STREAM_ERROR_FAILED:
176 MH_ERROR("** Encountered a GST_STREAM_ERROR_FAILED");
177 ret_error = media::Player::Error::resource_error;
178 break;
179 case GST_STREAM_ERROR_CODEC_NOT_FOUND:
180 MH_ERROR("** Encountered a GST_STREAM_ERROR_CODEC_NOT_FOUND");
181 // Missing codecs are handled later, when state switches to "paused"
182 ret_error = media::Player::Error::no_error;
183 break;
184 case GST_STREAM_ERROR_DECODE:
185 MH_ERROR("** Encountered a GST_STREAM_ERROR_DECODE");
186 ret_error = media::Player::Error::format_error;
187 break;
188 default:
189 MH_ERROR("** Encountered an unhandled stream error: '%s' code(%d)",
190 ewi.debug, ewi.error->code);
191 ret_error = media::Player::Error::no_error;
192 break;
193 }
194 }
195
196 if (ret_error != media::Player::Error::no_error) {
197 MH_ERROR("Resetting playbin pipeline after unrecoverable error: %s", ewi.debug);
198 playbin.reset();
199 }
200 return ret_error;
201 }
202
204 {
205 Q_Q(Engine);
206 const media::Player::Error e = from_gst_errorwarning(ewi);
207 if (e != media::Player::Error::no_error)
208 Q_EMIT q->errorOccurred(e);
209 }
210
212 {
213 Q_Q(Engine);
214 const media::Player::Error e = from_gst_errorwarning(ewi);
215 if (e != media::Player::Error::no_error)
216 Q_EMIT q->errorOccurred(e);
217 }
218
220 {
221 MH_DEBUG("Got a playbin info message (no action taken): %s", ewi.debug);
222 }
223
225 {
226 Q_Q(Engine);
227 media::Track::MetaData md;
228
229 // We update instead of creating from scratch if same uri
230 auto pair = q->trackMetadata();
231 if (playbin.uri() == pair.first)
232 md = pair.second;
233
235 q->setTrackMetadata(qMakePair(playbin.uri(), md));
236 }
237
239 Engine *q)
240 : playbin(key),
241 q_ptr(q)
242 {
243 QObject::connect(&playbin, &Playbin::errorOccurred,
244 q, [this](const Bus::Message::Detail::ErrorWarningInfo &ewi) {
245 on_playbin_error(ewi);
246 });
247 QObject::connect(&playbin, &Playbin::warningOccurred,
248 q, [this](const Bus::Message::Detail::ErrorWarningInfo &ewi) {
250 });
251 QObject::connect(&playbin, &Playbin::infoOccurred,
252 q, [this](const Bus::Message::Detail::ErrorWarningInfo &ewi) {
253 on_playbin_info(ewi);
254 });
255
256 QObject::connect(&playbin, &Playbin::aboutToFinish,
257 q, [this, q]() {
259 Q_EMIT q->aboutToFinish();
260 });
261 QObject::connect(&playbin, &Playbin::seekedTo,
262 q, &Engine::seekedTo);
263 QObject::connect(&playbin, &Playbin::bufferingChanged,
265 QObject::connect(&playbin, &Playbin::clientDisconnected,
267 QObject::connect(&playbin, &Playbin::endOfStream,
269
270 QObject::connect(&playbin, &Playbin::stateChanged,
271 q, [this](const Bus::Message::Detail::StateChanged &state,
272 const QByteArray &source) {
273 on_playbin_state_changed(state, source);
274 });
275
276 QObject::connect(&playbin, &Playbin::tagAvailable,
277 q, [this](const Bus::Message::Detail::Tag &tag) {
278 on_tag_available(tag);
279 });
280 QObject::connect(&playbin, &Playbin::orientationChanged,
281 q, [q](media::Player::Orientation o) {
282 q->setOrientation(o);
283 });
284 QObject::connect(&playbin, &Playbin::videoDimensionChanged,
285 q, [q](const QSize &size) {
286 q->setVideoDimension(size);
287 });
288 }
289
292};
293
294} // namespace
295
297 : d_ptr(new EnginePrivate(key, this))
298{
299 Q_D(Engine);
300
301 setMetadataExtractor(QSharedPointer<gstreamer::MetaDataExtractor>::create());
302
305
306 QObject::connect(&d->playbin, &Playbin::mediaFileTypeChanged,
307 this, [this, d]() {
308 const auto fileType = d->playbin.mediaFileType();
309 using ft = Playbin::MediaFileType;
310 setIsVideoSource(fileType == ft::MEDIA_FILE_TYPE_VIDEO);
311 setIsAudioSource(fileType == ft::MEDIA_FILE_TYPE_AUDIO);
312 });
313}
314
316{
317 stop();
318 setState(media::Engine::State::no_media);
319}
320
322 bool do_pipeline_reset)
323{
324 Q_D(Engine);
325 d->playbin.set_uri(uri, media::Player::HeadersType{}, do_pipeline_reset);
326 return true;
327}
328
331{
332 Q_D(Engine);
333 d->playbin.set_uri(uri, headers);
334 return true;
335}
336
338{
339 Q_D(Engine);
340 d->playbin.create_video_sink(texture_id);
341}
342
344{
345 Q_D(Engine);
346 const auto result = d->playbin.set_state(GST_STATE_PLAYING);
347
348 if (result)
349 {
350 setState(media::Engine::State::playing);
351 MH_INFO("Engine: playing uri: %s", qUtf8Printable(d->playbin.uri().toString()));
352 }
353
354 return result;
355}
356
358{
359 Q_D(Engine);
360 // No need to wait, and we can immediately return.
361 if (state() == media::Engine::State::stopped)
362 {
363 MH_DEBUG("Current player state is already stopped - no need to change state to stopped");
364 return true;
365 }
366
367 const auto result = d->playbin.set_state(GST_STATE_NULL);
368 if (result)
369 {
370 setState(media::Engine::State::stopped);
371 MH_TRACE("");
372 setPlaybackStatus(media::Player::stopped);
373 }
374
375 return result;
376}
377
379{
380 Q_D(Engine);
381 const auto result = d->playbin.set_state(GST_STATE_PAUSED);
382
383 if (result)
384 {
385 setState(media::Engine::State::paused);
386 MH_TRACE("");
387 }
388
389 return result;
390}
391
392bool gstreamer::Engine::seek_to(const std::chrono::microseconds& ts)
393{
394 Q_D(Engine);
395 return d->playbin.seek(ts);
396}
397
399{
400 Q_D(const Engine);
401 return d->playbin.position();
402}
403
405{
406 Q_D(const Engine);
407 return d->playbin.duration();
408}
409
411{
412 Q_D(Engine);
413 d->playbin.reset();
414}
415
416void gstreamer::Engine::doSetAudioStreamRole(media::Player::AudioStreamRole role)
417{
418 Q_D(Engine);
419 d->playbin.set_audio_stream_role(role);
420}
421
422void gstreamer::Engine::doSetLifetime(media::Player::Lifetime lifetime)
423{
424 Q_D(Engine);
425 d->playbin.set_lifetime(lifetime);
426}
427
429{
430 Q_D(Engine);
431 d->playbin.set_volume(volume);
432}
void on_playbin_info(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition engine.cpp:219
gstreamer::Playbin playbin
Definition engine.cpp:290
void on_tag_available(const gstreamer::Bus::Message::Detail::Tag &tag)
Definition engine.cpp:224
void on_playbin_error(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition engine.cpp:203
void on_playbin_warning(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition engine.cpp:211
void on_playbin_state_changed(const gstreamer::Bus::Message::Detail::StateChanged &state, const QByteArray &source)
Definition engine.cpp:73
media::Player::PlaybackStatus gst_state_to_player_status(const gstreamer::Bus::Message::Detail::StateChanged &state)
Definition engine.cpp:59
media::Player::Error from_gst_errorwarning(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition engine.cpp:105
EnginePrivate(const lomiri::MediaHubService::Player::PlayerKey key, Engine *q)
Definition engine.cpp:238
void doSetAudioStreamRole(lomiri::MediaHubService::Player::AudioStreamRole role) override
uint64_t duration() const
Definition engine.cpp:404
void doSetVolume(double volume) override
Definition engine.cpp:428
Engine(const lomiri::MediaHubService::Player::PlayerKey key)
Definition engine.cpp:296
bool open_resource_for_uri(const QUrl &uri, bool do_pipeline_reset)
Definition engine.cpp:321
void doSetLifetime(lomiri::MediaHubService::Player::Lifetime lifetime) override
void create_video_sink(uint32_t texture_id)
Definition engine.cpp:337
bool seek_to(const std::chrono::microseconds &ts)
Definition engine.cpp:392
uint64_t position() const
Definition engine.cpp:398
static void on_tag_available(const gstreamer::Bus::Message::Detail::Tag &tag, QVariantMap *md)
void stateChanged(const Bus::Message::Detail::StateChanged &state, const QByteArray &source)
void tagAvailable(Bus::Message::Detail::Tag tag)
bool can_play_streams() const
Definition playbin.cpp:817
void bufferingChanged(int progress)
void orientationChanged(lomiri::MediaHubService::Player::Orientation o)
void videoDimensionChanged(const QSize &size)
void warningOccurred(const Bus::Message::Detail::ErrorWarningInfo &)
void errorOccurred(const Bus::Message::Detail::ErrorWarningInfo &)
QUrl uri() const
Definition playbin.cpp:679
void mediaFileTypeChanged()
void seekedTo(uint64_t offset)
void infoOccurred(const Bus::Message::Detail::ErrorWarningInfo &)
Player::Lifetime lifetime() const
Definition engine.cpp:171
void setState(State state)
Definition engine.cpp:93
void setVideoDimension(const QSize &size)
Definition engine.cpp:204
void seekedTo(uint64_t offset)
void setMetadataExtractor(const QSharedPointer< MetaDataExtractor > &extractor)
Definition engine.cpp:81
void setOrientation(Player::Orientation o)
Definition engine.cpp:135
Player::AudioStreamRole audioStreamRole() const
Definition engine.cpp:157
QMap< QString, QString > HeadersType
Definition player.h:57
#define MH_TRACE(...)
Definition logging.h:37
#define MH_ERROR(...)
Definition logging.h:41
#define MH_INFO(...)
Definition logging.h:39
#define MH_DEBUG(...)
Definition logging.h:38
struct gstreamer::Init init