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 | |
47 | namespace Phonon { |
48 | namespace VLC { |
49 | |
50 | MediaPlayer::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 | |
100 | MediaPlayer::~MediaPlayer() |
101 | { |
102 | libvlc_media_player_release(p_mi: m_player); |
103 | } |
104 | |
105 | void 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 | |
111 | bool MediaPlayer::play() |
112 | { |
113 | m_doingPausedPlay = false; |
114 | return libvlc_media_player_play(p_mi: m_player) == 0; |
115 | } |
116 | |
117 | void MediaPlayer::pause() |
118 | { |
119 | m_doingPausedPlay = false; |
120 | libvlc_media_player_set_pause(mp: m_player, do_pause: 1); |
121 | } |
122 | |
123 | void MediaPlayer::pausedPlay() |
124 | { |
125 | m_doingPausedPlay = true; |
126 | libvlc_media_player_play(p_mi: m_player); |
127 | } |
128 | |
129 | void MediaPlayer::resume() |
130 | { |
131 | m_doingPausedPlay = false; |
132 | libvlc_media_player_set_pause(mp: m_player, do_pause: 0); |
133 | } |
134 | |
135 | void MediaPlayer::togglePause() |
136 | { |
137 | libvlc_media_player_pause(p_mi: m_player); |
138 | } |
139 | |
140 | void 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 | |
153 | qint64 MediaPlayer::length() const |
154 | { |
155 | return libvlc_media_player_get_length(p_mi: m_player); |
156 | } |
157 | |
158 | qint64 MediaPlayer::time() const |
159 | { |
160 | return libvlc_media_player_get_time(p_mi: m_player); |
161 | } |
162 | |
163 | void 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 | |
172 | bool MediaPlayer::isSeekable() const |
173 | { |
174 | return libvlc_media_player_is_seekable(p_mi: m_player); |
175 | } |
176 | |
177 | bool MediaPlayer::hasVideoOutput() const |
178 | { |
179 | return libvlc_media_player_has_vout(p_mi: m_player) > 0; |
180 | } |
181 | |
182 | bool MediaPlayer::setSubtitle(int subtitle) |
183 | { |
184 | return libvlc_video_set_spu(p_mi: m_player, i_spu: subtitle) == 0; |
185 | } |
186 | |
187 | bool 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 | |
195 | void MediaPlayer::setTitle(int title) |
196 | { |
197 | libvlc_media_player_set_title(p_mi: m_player, i_title: title); |
198 | } |
199 | |
200 | void MediaPlayer::setChapter(int chapter) |
201 | { |
202 | libvlc_media_player_set_chapter(p_mi: m_player, i_chapter: chapter); |
203 | } |
204 | |
205 | QImage 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 | |
217 | bool MediaPlayer::setAudioTrack(int track) |
218 | { |
219 | return libvlc_audio_set_track(p_mi: m_player, i_track: track) == 0; |
220 | } |
221 | |
222 | void 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 | |
338 | QDebug 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 | |
371 | void MediaPlayer::setAudioFade(qreal fade) |
372 | { |
373 | m_fadeAmount = fade; |
374 | setVolumeInternal(); |
375 | } |
376 | |
377 | void MediaPlayer::setAudioVolume(int volume) |
378 | { |
379 | m_volume = volume; |
380 | setVolumeInternal(); |
381 | } |
382 | |
383 | bool MediaPlayer::mute() const |
384 | { |
385 | return libvlc_audio_get_mute(p_mi: m_player); |
386 | } |
387 | |
388 | void MediaPlayer::setMute(bool mute) |
389 | { |
390 | libvlc_audio_set_mute(p_mi: m_player, status: mute); |
391 | } |
392 | |
393 | void MediaPlayer::setVolumeInternal() |
394 | { |
395 | libvlc_audio_set_volume(p_mi: m_player, i_volume: m_volume * m_fadeAmount); |
396 | } |
397 | |
398 | void 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 | |
415 | void 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 | |