/* GStreamer * * Copyright (C) 2015 Alexandre Moreno * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "qgstplayer.h" #include #include #include #include #include #include #include #include #include namespace QGstPlayer { class RegisterMetaTypes { public: RegisterMetaTypes() { qRegisterMetaType("State"); } } _register; MediaInfo::MediaInfo(Player *player) : QObject(player) , uri_() , title_() , isSeekable_(false) , videoStreams_() , audioStreams_() , subtitleStreams_() , sample_() { } QString MediaInfo::uri() const { return uri_; } QString MediaInfo::title() const { return title_; } bool MediaInfo::isSeekable() const { return isSeekable_; } const QList &MediaInfo::videoStreams() const { return videoStreams_; } const QList &MediaInfo::audioStreams() const { return audioStreams_; } const QList &MediaInfo::subtitleStreams() const { return subtitleStreams_; } const QImage &MediaInfo::sample() { return sample_; } void MediaInfo::update(GstPlayerMediaInfo *info) { Q_ASSERT(info != 0); // FIXME since media-info signal gets emitted // several times, we just update info iff the // media uri has changed. if (uri_ == gst_player_media_info_get_uri(info)) { return; } uri_ = QString(gst_player_media_info_get_uri(info)); title_ = QString::fromLocal8Bit(gst_player_media_info_get_title(info)); // if media has no title, return the file name if (title_.isEmpty()) { QUrl url(gst_player_media_info_get_uri(info)); title_ = url.fileName(); } emit titleChanged(); if (isSeekable_ != gst_player_media_info_is_seekable(info)) { isSeekable_ = !isSeekable_; emit seekableChanged(); } if (!subtitleStreams_.isEmpty()) { qDeleteAll(subtitleStreams_); subtitleStreams_.clear(); } if (!videoStreams_.isEmpty()) { qDeleteAll(videoStreams_); videoStreams_.clear(); } if (!audioStreams_.isEmpty()) { qDeleteAll(audioStreams_); audioStreams_.clear(); } GList *list = gst_player_get_subtitle_streams(info); g_list_foreach (list, [](gpointer data, gpointer user_data) { GstPlayerSubtitleInfo *info = static_cast(data); QList *subs = static_cast*>(user_data); subs->append(new SubtitleInfo(info)); }, &subtitleStreams_); list = gst_player_get_video_streams(info); g_list_foreach (list, [](gpointer data, gpointer user_data) { GstPlayerVideoInfo *info = static_cast(data); QList *videos = static_cast*>(user_data); videos->append(new VideoInfo(info)); }, &videoStreams_); list = gst_player_get_audio_streams(info); g_list_foreach (list, [](gpointer data, gpointer user_data) { GstPlayerAudioInfo *info = static_cast(data); QList *audios = static_cast*>(user_data); audios->append(new AudioInfo(info)); }, &audioStreams_); GstSample *sample; GstMapInfo map_info; GstBuffer *buffer; const GstStructure *caps_struct; GstTagImageType type = GST_TAG_IMAGE_TYPE_UNDEFINED; /* get image sample buffer from media */ sample = gst_player_media_info_get_image_sample (info); if (!sample) return; buffer = gst_sample_get_buffer (sample); caps_struct = gst_sample_get_info (sample); /* if sample is retrieved from preview-image tag then caps struct * will not be defined. */ if (caps_struct) gst_structure_get_enum (caps_struct, "image-type", GST_TYPE_TAG_IMAGE_TYPE, reinterpret_cast(&type)); /* FIXME: Should we check more type ?? */ if ((type != GST_TAG_IMAGE_TYPE_FRONT_COVER) && (type != GST_TAG_IMAGE_TYPE_UNDEFINED) && (type != GST_TAG_IMAGE_TYPE_NONE)) { g_print ("unsupport type ... %d \n", type); return; } if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ)) { g_print ("failed to map gst buffer \n"); return; } sample_ = QImage::fromData(map_info.data, map_info.size); if (sample_.isNull()) qWarning() << "failed to load media info sample image"; emit sampleChanged(); gst_buffer_unmap (buffer, &map_info); } VideoInfo::VideoInfo(GstPlayerVideoInfo *info) : StreamInfo(reinterpret_cast(info)) , video_(info) , resolution_(gst_player_video_info_get_width(info), gst_player_video_info_get_height(info)) { } QSize VideoInfo::resolution() const { return resolution_; } AudioInfo::AudioInfo(GstPlayerAudioInfo *info) : StreamInfo(reinterpret_cast(info)) , audio_(info) , language_(gst_player_audio_info_get_language(info)) , channels_(gst_player_audio_info_get_channels(info)) , bitRate_(gst_player_audio_info_get_bitrate(info)) , sampleRate_(gst_player_audio_info_get_sample_rate(info)) { } const QString &AudioInfo::language() const { return language_; } int AudioInfo::channels() const { return channels_; } int AudioInfo::bitRate() const { return bitRate_; } int AudioInfo::sampleRate() const { return sampleRate_; } SubtitleInfo::SubtitleInfo(GstPlayerSubtitleInfo *info) : StreamInfo(reinterpret_cast(info)) , subtitle_(info) , language_(gst_player_subtitle_info_get_language(info)) { } const QString &SubtitleInfo::language() const { return language_; } int StreamInfo::index() const { return index_; } StreamInfo::StreamInfo(GstPlayerStreamInfo *info) : stream_(info) , index_(gst_player_stream_info_get_index(info)) { } bool Player::isVideoAvailable() const { GstPlayerVideoInfo *video_info; video_info = gst_player_get_current_video_track (player_); if (video_info) { g_object_unref (video_info); return true; } return false; } MediaInfo *Player::mediaInfo() const { return mediaInfo_; } QVariant Player::currentVideo() const { Q_ASSERT(player_ != 0); GstPlayerVideoInfo *track = gst_player_get_current_video_track(player_); if (!track) return QVariant(); return QVariant::fromValue(new VideoInfo(track)); } QVariant Player::currentAudio() const { Q_ASSERT(player_ != 0); GstPlayerAudioInfo *track = gst_player_get_current_audio_track(player_); if (!track) return QVariant(); return QVariant::fromValue(new AudioInfo(track)); } QVariant Player::currentSubtitle() const { Q_ASSERT(player_ != 0); GstPlayerSubtitleInfo *track = gst_player_get_current_subtitle_track(player_); if (!track) return QVariant(); return QVariant::fromValue(new SubtitleInfo(track)); } void Player::setCurrentVideo(QVariant track) { Q_ASSERT(player_ != 0); VideoInfo* info = track.value(); Q_ASSERT(info); gst_player_set_video_track(player_, info->index()); } void Player::setCurrentAudio(QVariant track) { Q_ASSERT(player_ != 0); AudioInfo* info = track.value(); Q_ASSERT(info); gst_player_set_audio_track(player_, info->index()); } void Player::setCurrentSubtitle(QVariant track) { Q_ASSERT(player_ != 0); SubtitleInfo* info = track.value(); Q_ASSERT(info); gst_player_set_subtitle_track(player_, info->index()); } bool Player::isSubtitleEnabled() const { return subtitleEnabled_; } quint32 Player::positionUpdateInterval() const { Q_ASSERT(player_ != 0); return gst_player_get_position_update_interval(player_); } void Player::setSubtitleEnabled(bool enabled) { Q_ASSERT(player_ != 0); subtitleEnabled_ = enabled; gst_player_set_subtitle_track_enabled(player_, enabled); emit subtitleEnabledChanged(enabled); } void Player::setPositionUpdateInterval(quint32 interval) { Q_ASSERT(player_ != 0); gst_player_set_position_update_interval(player_, interval); } Player::Player(QObject *parent, VideoRenderer *renderer) : QObject(parent) , player_() , state_(STOPPED) , videoDimensions_(QSize()) , mediaInfo_() , videoAvailable_(false) , subtitleEnabled_(false) , autoPlay_(false) { player_ = gst_player_new_full(renderer ? renderer->renderer() : 0, gst_player_qt_signal_dispatcher_new(this)); g_object_connect(player_, "swapped-signal::state-changed", G_CALLBACK (Player::onStateChanged), this, "swapped-signal::position-updated", G_CALLBACK (Player::onPositionUpdated), this, "swapped-signal::duration-changed", G_CALLBACK (Player::onDurationChanged), this, "swapped-signal::buffering", G_CALLBACK (Player::onBufferingChanged), this, "swapped-signal::video-dimensions-changed", G_CALLBACK (Player::onVideoDimensionsChanged), this, "swapped-signal::volume-changed", G_CALLBACK (Player::onVolumeChanged), this, "swapped-signal::mute-changed", G_CALLBACK (Player::onMuteChanged), this, "swapped-signal::media-info-updated", G_CALLBACK (Player::onMediaInfoUpdated), this, "swapped-signal::end-of-stream", G_CALLBACK (Player::onEndOfStreamReached), this, NULL); mediaInfo_ = new MediaInfo(this); gst_player_set_subtitle_track_enabled(player_, false); } Player::~Player() { if (player_) { g_signal_handlers_disconnect_by_data(player_, this); gst_player_stop(player_); g_object_unref(player_); } } void Player::onStateChanged(Player * player, GstPlayerState state) { player->state_ = static_cast(state); emit player->stateChanged(player->state_); } void Player::onPositionUpdated(Player * player, GstClockTime position) { emit player->positionUpdated(position); } void Player::onDurationChanged(Player * player, GstClockTime duration) { emit player->durationChanged(duration); } void Player::onBufferingChanged(Player * player, int percent) { emit player->bufferingChanged(percent); } void Player::onVideoDimensionsChanged(Player * player, int w, int h) { QSize res(w,h); if (res == player->videoDimensions_) return; player->videoDimensions_ = res; player->setResolution(res); emit player->resolutionChanged(res); } void Player::onVolumeChanged(Player *player) { qreal new_val; new_val = gst_player_get_volume (player->player_); emit player->volumeChanged(new_val); } void Player::onMuteChanged(Player *player) { bool new_val; new_val = gst_player_get_mute (player->player_); emit player->mutedChanged(new_val); } void Player::onMediaInfoUpdated(Player *player, GstPlayerMediaInfo *media_info) { bool val = player->isVideoAvailable(); if (player->videoAvailable_ != val) { player->videoAvailable_ = val; emit player->videoAvailableChanged(val); } player->mediaInfo()->update(media_info); emit player->mediaInfoChanged(); } void Player::onEndOfStreamReached(Player *player) { Q_ASSERT(player != 0); emit player->endOfStream(); } void Player::setUri(QUrl url) { Q_ASSERT(player_ != 0); QByteArray uri = url.toString().toLocal8Bit(); gst_player_set_uri(player_, uri.data()); autoPlay_ ? play() : pause(); emit sourceChanged(url); } QList Player::playlist() const { return playlist_; } void Player::setPlaylist(const QList &playlist) { if (!playlist_.isEmpty()) { playlist_.erase(playlist_.begin(), playlist_.end()); } playlist_ = playlist; iter_ = playlist_.begin(); setUri(*iter_); } void Player::next() { if (playlist_.isEmpty()) return; if (iter_ == playlist_.end()) return; setUri(*++iter_); } void Player::previous() { if (playlist_.isEmpty()) return; if (iter_ == playlist_.begin()) return; setUri(*--iter_); } bool Player::autoPlay() const { return autoPlay_; } void Player::setAutoPlay(bool auto_play) { autoPlay_ = auto_play; if (autoPlay_) { connect(this, SIGNAL(endOfStream()), SLOT(next())); } } QUrl Player::source() const { Q_ASSERT(player_ != 0); QString url = QString::fromLocal8Bit(gst_player_get_uri(player_)); return QUrl(url); } qint64 Player::duration() const { Q_ASSERT(player_ != 0); return gst_player_get_duration(player_); } qint64 Player::position() const { Q_ASSERT(player_ != 0); return gst_player_get_position(player_); } qreal Player::volume() const { Q_ASSERT(player_ != 0); return gst_player_get_volume(player_); } bool Player::isMuted() const { Q_ASSERT(player_ != 0); return gst_player_get_mute(player_); } int Player::buffering() const { return 0; } QSize Player::resolution() const { return videoDimensions_; } void Player::setResolution(QSize size) { videoDimensions_ = size; } Player::State Player::state() const { return state_; } GstElement *Player::pipeline() const { Q_ASSERT(player_ != 0); return gst_player_get_pipeline(player_); } void Player::play() { Q_ASSERT(player_ != 0); gst_player_play(player_); } void Player::pause() { Q_ASSERT(player_ != 0); gst_player_pause(player_); } void Player::stop() { Q_ASSERT(player_ != 0); gst_player_stop(player_); } void Player::seek(qint64 position) { Q_ASSERT(player_ != 0); gst_player_seek(player_, position); } void Player::setSource(QUrl const& url) { Q_ASSERT(player_ != 0); // discard playlist if (!playlist_.isEmpty()) { playlist_.erase(playlist_.begin(), playlist_.end()); } setUri(url); emit sourceChanged(url); } void Player::setVolume(qreal val) { Q_ASSERT(player_ != 0); gst_player_set_volume(player_, val); } void Player::setMuted(bool val) { Q_ASSERT(player_ != 0); gst_player_set_mute(player_, val); } void Player::setPosition(qint64 pos) { Q_ASSERT(player_ != 0); gst_player_seek(player_, pos); } GstPlayerVideoRenderer *VideoRenderer::renderer() { return static_cast (gst_object_ref (renderer_)); } VideoRenderer::VideoRenderer() { renderer_ = static_cast (g_object_new (GST_TYPE_PLAYER_QT_VIDEO_RENDERER, "renderer", this, NULL)); } VideoRenderer::~VideoRenderer() { if (renderer_) gst_object_unref(renderer_); } } struct _GstPlayerQtVideoRenderer { GObject parent; gpointer renderer; }; struct _GstPlayerQtVideoRendererClass { GObjectClass parent_class; }; static void gst_player_qt_video_renderer_interface_init (GstPlayerVideoRendererInterface * iface); G_DEFINE_TYPE_WITH_CODE (GstPlayerQtVideoRenderer, gst_player_qt_video_renderer, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_VIDEO_RENDERER, gst_player_qt_video_renderer_interface_init)) enum { QT_VIDEO_RENDERER_PROP_0, QT_VIDEO_RENDERER_PROP_RENDERER, QT_VIDEO_RENDERER_PROP_LAST }; static GParamSpec * qt_video_renderer_param_specs [QT_VIDEO_RENDERER_PROP_LAST] = { NULL, }; static void gst_player_qt_video_renderer_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object); switch (prop_id) { case QT_VIDEO_RENDERER_PROP_RENDERER: self->renderer = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_player_qt_video_renderer_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (object); switch (prop_id) { case QT_VIDEO_RENDERER_PROP_RENDERER: g_value_set_pointer (value, self->renderer); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_player_qt_video_renderer_finalize (GObject * object) { G_OBJECT_CLASS (gst_player_qt_video_renderer_parent_class)->finalize(object); } static void gst_player_qt_video_renderer_class_init (GstPlayerQtVideoRendererClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = gst_player_qt_video_renderer_set_property; gobject_class->get_property = gst_player_qt_video_renderer_get_property; gobject_class->finalize = gst_player_qt_video_renderer_finalize; qt_video_renderer_param_specs [QT_VIDEO_RENDERER_PROP_RENDERER] = g_param_spec_pointer ("renderer", "Qt Renderer", "", static_cast(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (gobject_class, QT_VIDEO_RENDERER_PROP_LAST, qt_video_renderer_param_specs); } static void gst_player_qt_video_renderer_init (G_GNUC_UNUSED GstPlayerQtVideoRenderer * self) { } static GstElement * gst_player_qt_video_renderer_create_video_sink (GstPlayerVideoRenderer * iface, G_GNUC_UNUSED GstPlayer *player) { GstPlayerQtVideoRenderer *self = GST_PLAYER_QT_VIDEO_RENDERER (iface); g_assert(self->renderer != NULL); return static_cast(self->renderer)->createVideoSink(); } static void gst_player_qt_video_renderer_interface_init (GstPlayerVideoRendererInterface * iface) { iface->create_video_sink = gst_player_qt_video_renderer_create_video_sink; } struct _GstPlayerQtSignalDispatcher { GObject parent; gpointer player; }; struct _GstPlayerQtSignalDispatcherClass { GObjectClass parent_class; }; static void gst_player_qt_signal_dispatcher_interface_init (GstPlayerSignalDispatcherInterface * iface); enum { QT_SIGNAL_DISPATCHER_PROP_0, QT_SIGNAL_DISPATCHER_PROP_PLAYER, QT_SIGNAL_DISPATCHER_PROP_LAST }; G_DEFINE_TYPE_WITH_CODE (GstPlayerQtSignalDispatcher, gst_player_qt_signal_dispatcher, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GST_TYPE_PLAYER_SIGNAL_DISPATCHER, gst_player_qt_signal_dispatcher_interface_init)); static GParamSpec * qt_signal_dispatcher_param_specs [QT_SIGNAL_DISPATCHER_PROP_LAST] = { NULL, }; static void gst_player_qt_signal_dispatcher_finalize (GObject * object) { G_OBJECT_CLASS (gst_player_qt_signal_dispatcher_parent_class)->finalize (object); } static void gst_player_qt_signal_dispatcher_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstPlayerQtSignalDispatcher *self = GST_PLAYER_QT_SIGNAL_DISPATCHER (object); switch (prop_id) { case QT_SIGNAL_DISPATCHER_PROP_PLAYER: self->player = g_value_get_pointer (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_player_qt_signal_dispatcher_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstPlayerQtSignalDispatcher *self = GST_PLAYER_QT_SIGNAL_DISPATCHER (object); switch (prop_id) { case QT_SIGNAL_DISPATCHER_PROP_PLAYER: g_value_set_pointer (value, self->player); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_player_qt_signal_dispatcher_class_init (GstPlayerQtSignalDispatcherClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = gst_player_qt_signal_dispatcher_finalize; gobject_class->set_property = gst_player_qt_signal_dispatcher_set_property; gobject_class->get_property = gst_player_qt_signal_dispatcher_get_property; qt_signal_dispatcher_param_specs [QT_SIGNAL_DISPATCHER_PROP_PLAYER] = g_param_spec_pointer ("player", "Player instance", "", static_cast(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_properties (gobject_class, QT_SIGNAL_DISPATCHER_PROP_LAST, qt_signal_dispatcher_param_specs); } static void gst_player_qt_signal_dispatcher_init (G_GNUC_UNUSED GstPlayerQtSignalDispatcher * self) { } static void gst_player_qt_signal_dispatcher_dispatch (GstPlayerSignalDispatcher * iface, G_GNUC_UNUSED GstPlayer * player, void (*emitter) (gpointer data), gpointer data, GDestroyNotify destroy) { GstPlayerQtSignalDispatcher *self = GST_PLAYER_QT_SIGNAL_DISPATCHER (iface); QObject dispatch; QObject *receiver = static_cast(self->player); QObject::connect(&dispatch, &QObject::destroyed, receiver, [=]() { emitter(data); if (destroy) destroy(data); }, Qt::QueuedConnection); } static void gst_player_qt_signal_dispatcher_interface_init (GstPlayerSignalDispatcherInterface * iface) { iface->dispatch = gst_player_qt_signal_dispatcher_dispatch; } GstPlayerSignalDispatcher * gst_player_qt_signal_dispatcher_new (gpointer player) { return static_cast (g_object_new (GST_TYPE_PLAYER_QT_SIGNAL_DISPATCHER, "player", player, NULL)); }