1/*
2 Copyright (C) 2011-2018 Harald Sitter <sitter@kde.org>
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Lesser General Public
6 License as published by the Free Software Foundation; either
7 version 2.1 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public
15 License along with this library. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "mediaplayer.h"
19
20#include <QtCore/QDebug>
21#include <QtCore/QDir>
22#include <QtCore/QMetaType>
23#include <QtCore/QString>
24#include <QtCore/QTemporaryFile>
25#include <QtGui/QImage>
26
27#include <vlc/libvlc_version.h>
28
29#include "utils/libvlc.h"
30#include "media.h"
31
32// Callbacks come from a VLC thread. In some cases Qt fails to detect this and
33// tries to invoke directly (i.e. from same thread). This can lead to thread
34// pollution throughout Phonon, which is very much not desired.
35#define P_EMIT_HAS_VIDEO(hasVideo) \
36 QMetaObject::invokeMethod(\
37 that, "hasVideoChanged", \
38 Qt::QueuedConnection, \
39 Q_ARG(bool, hasVideo))
40
41#define P_EMIT_STATE(__state) \
42 QMetaObject::invokeMethod(\
43 that, "stateChanged", \
44 Qt::QueuedConnection, \
45 Q_ARG(MediaPlayer::State, __state))
46
47namespace Phonon {
48namespace VLC {
49
50MediaPlayer::MediaPlayer(QObject *parent)
51 : QObject(parent)
52 , m_media(0)
53 , m_player(libvlc_media_player_new(pvlc_libvlc))
54 , m_doingPausedPlay(false)
55 , m_volume(75)
56 , m_fadeAmount(1.0f)
57{
58 Q_ASSERT(m_player);
59
60 qRegisterMetaType<MediaPlayer::State>(typeName: "MediaPlayer::State");
61
62 libvlc_event_manager_t *manager = libvlc_media_player_event_manager(p_mi: m_player);
63 libvlc_event_type_t events[] = {
64 libvlc_MediaPlayerMediaChanged,
65 libvlc_MediaPlayerNothingSpecial,
66 libvlc_MediaPlayerOpening,
67 libvlc_MediaPlayerBuffering,
68 libvlc_MediaPlayerPlaying,
69 libvlc_MediaPlayerPaused,
70 libvlc_MediaPlayerStopped,
71 libvlc_MediaPlayerForward,
72 libvlc_MediaPlayerBackward,
73 libvlc_MediaPlayerEndReached,
74 libvlc_MediaPlayerEncounteredError,
75 libvlc_MediaPlayerTimeChanged,
76 libvlc_MediaPlayerPositionChanged,
77 libvlc_MediaPlayerSeekableChanged,
78 libvlc_MediaPlayerPausableChanged,
79 libvlc_MediaPlayerTitleChanged,
80 libvlc_MediaPlayerSnapshotTaken,
81 libvlc_MediaPlayerLengthChanged,
82 libvlc_MediaPlayerVout,
83 libvlc_MediaPlayerCorked,
84 libvlc_MediaPlayerUncorked,
85 libvlc_MediaPlayerMuted,
86 libvlc_MediaPlayerUnmuted,
87 libvlc_MediaPlayerAudioVolume
88 };
89 const int eventCount = sizeof(events) / sizeof(*events);
90 for (int i = 0; i < eventCount; ++i) {
91 libvlc_event_attach(p_event_manager: manager, i_event_type: events[i], f_callback: event_cb, user_data: this);
92 }
93
94 // Deactivate video title overlay (i.e. name of the video displaying
95 // at start. Since 2.1 that is handled via the API which in general is more
96 // reliable than setting it via libvlc_new (or so I have been told....)
97 libvlc_media_player_set_video_title_display(p_mi: m_player, position: libvlc_position_disable, timeout: 0);
98}
99
100MediaPlayer::~MediaPlayer()
101{
102 libvlc_media_player_release(p_mi: m_player);
103}
104
105void MediaPlayer::setMedia(Media *media)
106{
107 m_media = media;
108 libvlc_media_player_set_media(p_mi: m_player, p_md: *m_media);
109}
110
111bool MediaPlayer::play()
112{
113 m_doingPausedPlay = false;
114 return libvlc_media_player_play(p_mi: m_player) == 0;
115}
116
117void MediaPlayer::pause()
118{
119 m_doingPausedPlay = false;
120 libvlc_media_player_set_pause(mp: m_player, do_pause: 1);
121}
122
123void MediaPlayer::pausedPlay()
124{
125 m_doingPausedPlay = true;
126 libvlc_media_player_play(p_mi: m_player);
127}
128
129void MediaPlayer::resume()
130{
131 m_doingPausedPlay = false;
132 libvlc_media_player_set_pause(mp: m_player, do_pause: 0);
133}
134
135void MediaPlayer::togglePause()
136{
137 libvlc_media_player_pause(p_mi: m_player);
138}
139
140void MediaPlayer::stop()
141{
142 m_doingPausedPlay = false;
143#ifdef __GNUC__
144#warning changed to stop_async does this have impliciations
145#endif
146#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0))
147 libvlc_media_player_stop_async(m_player);
148#else
149 libvlc_media_player_stop(p_mi: m_player);
150#endif
151}
152
153qint64 MediaPlayer::length() const
154{
155 return libvlc_media_player_get_length(p_mi: m_player);
156}
157
158qint64 MediaPlayer::time() const
159{
160 return libvlc_media_player_get_time(p_mi: m_player);
161}
162
163void MediaPlayer::setTime(qint64 newTime)
164{
165#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0))
166 libvlc_media_player_set_time(m_player, newTime, false /* not fast, but precise */);
167#else
168 libvlc_media_player_set_time(p_mi: m_player, i_time: newTime);
169#endif
170}
171
172bool MediaPlayer::isSeekable() const
173{
174 return libvlc_media_player_is_seekable(p_mi: m_player);
175}
176
177bool MediaPlayer::hasVideoOutput() const
178{
179 return libvlc_media_player_has_vout(p_mi: m_player) > 0;
180}
181
182bool MediaPlayer::setSubtitle(int subtitle)
183{
184 return libvlc_video_set_spu(p_mi: m_player, i_spu: subtitle) == 0;
185}
186
187bool MediaPlayer::setSubtitle(const QString &file)
188{
189 return libvlc_media_player_add_slave(p_mi: m_player,
190 i_type: libvlc_media_slave_type_subtitle,
191 psz_uri: file.toUtf8().data(),
192 b_select: true) == 0;
193}
194
195void MediaPlayer::setTitle(int title)
196{
197 libvlc_media_player_set_title(p_mi: m_player, i_title: title);
198}
199
200void MediaPlayer::setChapter(int chapter)
201{
202 libvlc_media_player_set_chapter(p_mi: m_player, i_chapter: chapter);
203}
204
205QImage MediaPlayer::snapshot() const
206{
207 QTemporaryFile tempFile(QDir::tempPath() % QDir::separator() % QStringLiteral("phonon-vlc-snapshot"));
208 tempFile.open();
209
210 // This function is sync.
211 if (libvlc_video_take_snapshot(p_mi: m_player, num: 0, psz_filepath: tempFile.fileName().toLocal8Bit().data(), i_width: 0, i_height: 0) != 0)
212 return QImage();
213
214 return QImage(tempFile.fileName());
215}
216
217bool MediaPlayer::setAudioTrack(int track)
218{
219 return libvlc_audio_set_track(p_mi: m_player, i_track: track) == 0;
220}
221
222void MediaPlayer::event_cb(const libvlc_event_t *event, void *opaque)
223{
224 MediaPlayer *that = reinterpret_cast<MediaPlayer *>(opaque);
225 Q_ASSERT(that);
226
227 // Do not forget to register for the events you want to handle here!
228 switch (event->type) {
229 case libvlc_MediaPlayerTimeChanged:
230 QMetaObject::invokeMethod(
231 obj: that, member: "timeChanged",
232 c: Qt::QueuedConnection,
233 Q_ARG(qint64, event->u.media_player_time_changed.new_time));
234 break;
235 case libvlc_MediaPlayerSeekableChanged:
236 QMetaObject::invokeMethod(
237 obj: that, member: "seekableChanged",
238 c: Qt::QueuedConnection,
239 Q_ARG(bool, event->u.media_player_seekable_changed.new_seekable));
240 break;
241 case libvlc_MediaPlayerLengthChanged:
242 QMetaObject::invokeMethod(
243 obj: that, member: "lengthChanged",
244 c: Qt::QueuedConnection,
245 Q_ARG(qint64, event->u.media_player_length_changed.new_length));
246 break;
247 case libvlc_MediaPlayerNothingSpecial:
248 P_EMIT_STATE(NoState);
249 break;
250 case libvlc_MediaPlayerOpening:
251 P_EMIT_STATE(OpeningState);
252 break;
253 case libvlc_MediaPlayerBuffering:
254 QMetaObject::invokeMethod(
255 obj: that, member: "bufferChanged",
256 c: Qt::QueuedConnection,
257 Q_ARG(int, event->u.media_player_buffering.new_cache));
258 break;
259 case libvlc_MediaPlayerPlaying:
260 // Intercept state change and apply pausing once playing.
261 if (that->m_doingPausedPlay) {
262 that->m_doingPausedPlay = false;
263 // VLC internally will call stop if a player can not be paused, this
264 // can lead to deadlocks as stop is partially blocking, to avoid this
265 // we explicitly do a queued stop whenever a player can not be paused.
266 // In those situations pausedplay capability can not be provided, so
267 // applications wanting to do pausedplay will need to handle it anyway
268 // as faking a paused state when there is none would be a very code
269 // intense workaround asking for weird abstraction leakage.
270 // See kde bug 337604.
271 if (libvlc_media_player_can_pause(p_mi: that->m_player)) {
272 that->pause();
273 } else {
274 QMetaObject::invokeMethod(obj: that, member: "stop", c: Qt::QueuedConnection);
275 }
276 } else
277 P_EMIT_STATE(PlayingState);
278 break;
279 case libvlc_MediaPlayerPaused:
280 P_EMIT_STATE(PausedState);
281 break;
282 case libvlc_MediaPlayerStopped:
283 P_EMIT_STATE(StoppedState);
284 break;
285 case libvlc_MediaPlayerEndReached:
286 P_EMIT_STATE(EndedState);
287 break;
288 case libvlc_MediaPlayerEncounteredError:
289 P_EMIT_STATE(ErrorState);
290 break;
291 case libvlc_MediaPlayerVout:
292 if (event->u.media_player_vout.new_count > 0) {
293 P_EMIT_HAS_VIDEO(true);
294 } else {
295 P_EMIT_HAS_VIDEO(false);
296 }
297 break;
298 case libvlc_MediaPlayerMediaChanged:
299 break;
300 case libvlc_MediaPlayerCorked:
301 that->pause();
302 break;
303 case libvlc_MediaPlayerUncorked:
304 that->play();
305 break;
306 case libvlc_MediaPlayerMuted:
307 QMetaObject::invokeMethod(
308 obj: that, member: "mutedChanged",
309 c: Qt::QueuedConnection,
310 Q_ARG(bool, true));
311 break;
312 case libvlc_MediaPlayerUnmuted:
313 QMetaObject::invokeMethod(
314 obj: that, member: "mutedChanged",
315 c: Qt::QueuedConnection,
316 Q_ARG(bool, false));
317 break;
318 case libvlc_MediaPlayerAudioVolume:
319 QMetaObject::invokeMethod(
320 obj: that, member: "volumeChanged",
321 c: Qt::QueuedConnection,
322 Q_ARG(float, event->u.media_player_audio_volume.volume));
323 break;
324 case libvlc_MediaPlayerForward:
325 case libvlc_MediaPlayerBackward:
326 case libvlc_MediaPlayerPositionChanged:
327 case libvlc_MediaPlayerPausableChanged:
328 case libvlc_MediaPlayerTitleChanged:
329 case libvlc_MediaPlayerSnapshotTaken: // Snapshot call is sync, so this is useless.
330 default:
331 break;
332 QString msg = QString("Unknown event: ") + QString::number(event->type);
333 Q_ASSERT_X(false, "event_cb", qPrintable(msg));
334 break;
335 }
336}
337
338QDebug operator<<(QDebug dbg, const MediaPlayer::State &s)
339{
340 QString name;
341 switch (s) {
342 case MediaPlayer::NoState:
343 name = QLatin1String("MediaPlayer::NoState");
344 break;
345 case MediaPlayer::OpeningState:
346 name = QLatin1String("MediaPlayer::OpeningState");
347 break;
348 case MediaPlayer::BufferingState:
349 name = QLatin1String("MediaPlayer::BufferingState");
350 break;
351 case MediaPlayer::PlayingState:
352 name = QLatin1String("MediaPlayer::PlayingState");
353 break;
354 case MediaPlayer::PausedState:
355 name = QLatin1String("MediaPlayer::PausedState");
356 break;
357 case MediaPlayer::StoppedState:
358 name = QLatin1String("MediaPlayer::StoppedState");
359 break;
360 case MediaPlayer::EndedState:
361 name = QLatin1String("MediaPlayer::EndedState");
362 break;
363 case MediaPlayer::ErrorState:
364 name = QLatin1String("MediaPlayer::ErrorState");
365 break;
366 }
367 dbg.nospace() << "State(" << qPrintable(name) << ")";
368 return dbg.space();
369}
370
371void MediaPlayer::setAudioFade(qreal fade)
372{
373 m_fadeAmount = fade;
374 setVolumeInternal();
375}
376
377void MediaPlayer::setAudioVolume(int volume)
378{
379 m_volume = volume;
380 setVolumeInternal();
381}
382
383bool MediaPlayer::mute() const
384{
385 return libvlc_audio_get_mute(p_mi: m_player);
386}
387
388void MediaPlayer::setMute(bool mute)
389{
390 libvlc_audio_set_mute(p_mi: m_player, status: mute);
391}
392
393void MediaPlayer::setVolumeInternal()
394{
395 libvlc_audio_set_volume(p_mi: m_player, i_volume: m_volume * m_fadeAmount);
396}
397
398void MediaPlayer::setCdTrack(int track)
399{
400 if (!m_media)
401 return;
402#ifdef __GNUC__
403#warning changed to stop_async does this have impliciations
404#endif
405#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(4, 0, 0, 0))
406 libvlc_media_player_stop_async(m_player);
407#else
408 libvlc_media_player_stop(p_mi: m_player);
409#endif
410 m_media->setCdTrack(track);
411 libvlc_media_player_set_media(p_mi: m_player, p_md: *m_media);
412 libvlc_media_player_play(p_mi: m_player);
413}
414
415void MediaPlayer::setEqualizer(libvlc_equalizer_t *equalizer)
416{
417 libvlc_media_player_set_equalizer(p_mi: m_player, p_equalizer: equalizer);
418}
419
420} // namespace VLC
421} // namespace Phonon
422

source code of phonon-vlc/src/mediaplayer.cpp