Music Hub  ..
A session-wide music playback service
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 
35 namespace media = lomiri::MediaHubService;
36 
37 using namespace std;
38 
39 namespace gstreamer
40 {
41 struct Init
42 {
43  Init()
44  {
45  gst_init(nullptr, nullptr);
46  }
47 
49  {
50  gst_deinit();
51  }
52 } init;
53 
55 {
56  Q_DECLARE_PUBLIC(Engine)
57 
58 public:
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 &&
88  !playbin.can_play_streams()) {
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) {
249  on_playbin_warning(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]() {
258  q->setState(Engine::State::ready);
259  Q_EMIT q->aboutToFinish();
260  });
261  QObject::connect(&playbin, &Playbin::seekedTo,
262  q, &Engine::seekedTo);
263  QObject::connect(&playbin, &Playbin::bufferingChanged,
264  q, &Engine::bufferingChanged);
265  QObject::connect(&playbin, &Playbin::clientDisconnected,
266  q, &Engine::clientDisconnected);
267  QObject::connect(&playbin, &Playbin::endOfStream,
268  q, &Engine::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 
337 void gstreamer::Engine::create_video_sink(uint32_t texture_id)
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 
392 bool 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 
417 {
418  Q_D(Engine);
419  d->playbin.set_audio_stream_role(role);
420 }
421 
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 }
lomiri::MediaHubService::Engine::setState
void setState(State state)
Definition: engine.cpp:93
gstreamer::Playbin
Definition: playbin.h:46
gstreamer::EnginePrivate::q_ptr
Engine * q_ptr
Definition: engine.cpp:291
lomiri::MediaHubService::Player::Error
Error
Definition: player.h:135
gstreamer::Init
Definition: engine.cpp:41
meta_data_extractor.h
gstreamer::Engine::reset
void reset()
Definition: engine.cpp:410
gstreamer::Engine::Engine
Engine(const lomiri::MediaHubService::Player::PlayerKey key)
Definition: engine.cpp:296
gstreamer::Bus::Message::Detail::StateChanged::new_state
GstState new_state
Definition: bus.h:215
gstreamer::EnginePrivate::on_playbin_info
void on_playbin_info(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition: engine.cpp:219
gstreamer::Engine::create_video_sink
void create_video_sink(uint32_t texture_id)
Definition: engine.cpp:337
gstreamer::EnginePrivate::on_playbin_warning
void on_playbin_warning(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition: engine.cpp:211
gstreamer::Engine::pause
bool pause()
Definition: engine.cpp:378
gstreamer::Bus::Message::Detail::ErrorWarningInfo::error
GError * error
Definition: bus.h:192
gstreamer::Engine::stop
bool stop()
Definition: engine.cpp:357
gstreamer::Engine::seek_to
bool seek_to(const std::chrono::microseconds &ts)
Definition: engine.cpp:392
gstreamer::EnginePrivate::gst_state_to_player_status
media::Player::PlaybackStatus gst_state_to_player_status(const gstreamer::Bus::Message::Detail::StateChanged &state)
Definition: engine.cpp:59
lomiri::MediaHubService::Engine::lifetime
Player::Lifetime lifetime() const
Definition: engine.cpp:171
gstreamer::EnginePrivate::EnginePrivate
EnginePrivate(const lomiri::MediaHubService::Player::PlayerKey key, Engine *q)
Definition: engine.cpp:238
gstreamer::Engine::doSetVolume
void doSetVolume(double volume) override
Definition: engine.cpp:428
gstreamer::Playbin::mediaFileTypeChanged
void mediaFileTypeChanged()
lomiri::MediaHubService::Player::HeadersType
QMap< QString, QString > HeadersType
Definition: player.h:57
gstreamer::EnginePrivate::from_gst_errorwarning
media::Player::Error from_gst_errorwarning(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition: engine.cpp:105
gstreamer::Engine::position
uint64_t position() const
Definition: engine.cpp:398
gstreamer::Bus::Message::Detail::ErrorWarningInfo
Definition: bus.h:190
lomiri::MediaHubService::Player::AudioStreamRole
AudioStreamRole
Definition: player.h:113
lomiri::MediaHubService::Engine::setVideoDimension
void setVideoDimension(const QSize &size)
Definition: engine.cpp:204
gstreamer::Init::Init
Init()
Definition: engine.cpp:43
gstreamer::MetaDataExtractor::on_tag_available
static void on_tag_available(const gstreamer::Bus::Message::Detail::Tag &tag, QVariantMap *md)
Definition: meta_data_extractor.h:65
gstreamer
Definition: bus.h:34
lomiri::MediaHubService::Player::PlayerKey
uint32_t PlayerKey
Definition: player.h:55
gstreamer::Engine::doSetAudioStreamRole
void doSetAudioStreamRole(lomiri::MediaHubService::Player::AudioStreamRole role) override
gstreamer::EnginePrivate::on_playbin_state_changed
void on_playbin_state_changed(const gstreamer::Bus::Message::Detail::StateChanged &state, const QByteArray &source)
Definition: engine.cpp:73
gstreamer::EnginePrivate::playbin
gstreamer::Playbin playbin
Definition: engine.cpp:290
gstreamer::Engine::doSetLifetime
void doSetLifetime(lomiri::MediaHubService::Player::Lifetime lifetime) override
MH_ERROR
#define MH_ERROR(...)
Definition: logging.h:41
lomiri::MediaHubService::Engine::setMetadataExtractor
void setMetadataExtractor(const QSharedPointer< MetaDataExtractor > &extractor)
Definition: engine.cpp:81
MH_TRACE
#define MH_TRACE(...)
Definition: logging.h:37
lomiri::MediaHubService::Engine::audioStreamRole
Player::AudioStreamRole audioStreamRole() const
Definition: engine.cpp:157
lomiri::MediaHubService
Definition: context.h:28
lomiri::MediaHubService::Player::stopped
@ stopped
Definition: player.h:98
gstreamer::Engine::~Engine
~Engine()
Definition: engine.cpp:315
lomiri::MediaHubService::Engine::setOrientation
void setOrientation(Player::Orientation o)
Definition: engine.cpp:135
lomiri::MediaHubService::Player::PlaybackStatus
PlaybackStatus
Definition: player.h:92
lomiri::MediaHubService::Player::Lifetime
Lifetime
Definition: player.h:129
gstreamer::Engine::duration
uint64_t duration() const
Definition: engine.cpp:404
gstreamer::Bus::Message::Detail::ErrorWarningInfo::debug
gchar * debug
Definition: bus.h:193
gstreamer::EnginePrivate::on_tag_available
void on_tag_available(const gstreamer::Bus::Message::Detail::Tag &tag)
Definition: engine.cpp:224
gstreamer::Engine::play
bool play()
Definition: engine.cpp:343
MH_DEBUG
#define MH_DEBUG(...)
Definition: logging.h:38
gstreamer::EnginePrivate::on_playbin_error
void on_playbin_error(const gstreamer::Bus::Message::Detail::ErrorWarningInfo &ewi)
Definition: engine.cpp:203
MH_INFO
#define MH_INFO(...)
Definition: logging.h:39
gstreamer::Engine::open_resource_for_uri
bool open_resource_for_uri(const QUrl &uri, bool do_pipeline_reset)
Definition: engine.cpp:321
gstreamer::Init::~Init
~Init()
Definition: engine.cpp:48
gstreamer::init
struct gstreamer::Init init
gstreamer::EnginePrivate
Definition: engine.cpp:54
gstreamer::Engine
Definition: engine.h:34
lomiri::MediaHubService::Engine::aboutToFinish
void aboutToFinish()
engine.h
gstreamer::Bus::Message::Detail::StateChanged
Definition: bus.h:212
bus.h
gstreamer::Bus::Message::Detail::Tag
Definition: bus.h:195
playbin.h
lomiri::MediaHubService::Player::Orientation
Orientation
Definition: player.h:121