1 /*
2  * Cantata
3  *
4  * Copyright (c) 2011-2020 Craig Drummond <craig.p.drummond@gmail.com>
5  *
6  * ----
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; see the file COPYING.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #include "httpstream.h"
25 #include "mpdconnection.h"
26 #include "mpdstatus.h"
27 #include "gui/settings.h"
28 #include "support/globalstatic.h"
29 #include "support/configuration.h"
30 #ifndef LIBVLC_FOUND
31 #include <QtMultimedia/QMediaPlayer>
32 #endif
33 #include <QTimer>
34 
35 static const int constPlayerCheckPeriod = 250;
36 static const int constMaxPlayStateChecks= 2000 / constPlayerCheckPeriod;
37 
38 #include <QDebug>
39 static bool debugEnabled = false;
40 #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__
enableDebug()41 void HttpStream::enableDebug()
42 {
43     debugEnabled = true;
44 }
45 
GLOBAL_STATIC(HttpStream,instance)46 GLOBAL_STATIC(HttpStream, instance)
47 
48 HttpStream::HttpStream(QObject *p)
49     : QObject(p)
50     , enabled(false)
51     , muted(false)
52     , state(MPDState_Inactive)
53     , playStateChecks(0)
54     , currentVolume(50)
55     , unmuteVol(50)
56     , playStateCheckTimer(nullptr)
57     , player(nullptr)
58 {
59 }
60 
save() const61 void HttpStream::save() const
62 {
63     Configuration config(metaObject()->className());
64     config.set("volume", currentVolume);
65 }
66 
setEnabled(bool e)67 void HttpStream::setEnabled(bool e)
68 {
69     if (e == enabled) {
70         return;
71     }
72 
73     enabled = e;
74     if (enabled) {
75         connect(MPDConnection::self(), SIGNAL(streamUrl(QString)), this, SLOT(streamUrl(QString)));
76         connect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus()));
77         streamUrl(MPDConnection::self()->getDetails().streamUrl);
78     } else {
79         disconnect(MPDConnection::self(), SIGNAL(streamUrl(QString)), this, SLOT(streamUrl(QString)));
80         disconnect(MPDStatus::self(), SIGNAL(updated()), this, SLOT(updateStatus()));
81         if (player) {
82             save();
83             #ifdef LIBVLC_FOUND
84             libvlc_media_player_stop(player);
85             #else
86             player->stop();
87             #endif
88         }
89     }
90     emit isEnabled(enabled);
91 }
92 
setVolume(int vol)93 void HttpStream::setVolume(int vol)
94 {
95     DBUG << vol;
96     if (player) {
97         currentVolume = vol;
98         #ifdef LIBVLC_FOUND
99         libvlc_audio_set_volume(player, vol);
100         #else
101         player->setVolume(vol);
102         #endif
103         emit update();
104     }
105 }
106 
volume()107 int HttpStream::volume()
108 {
109     if (!enabled) {
110         return -1;
111     }
112 
113     int vol = currentVolume;
114     if (player && !isMuted()) {
115         #ifdef LIBVLC_FOUND
116         vol = libvlc_audio_get_volume(player);
117         #else
118         vol = player->volume();
119         #endif
120         if (vol < 0) {
121             vol = currentVolume;
122         } else {
123             currentVolume = vol;
124         }
125     }
126     DBUG << vol;
127     return vol;
128 }
129 
toggleMute()130 void HttpStream::toggleMute()
131 {
132     DBUG << isMuted();
133     if (player) {
134         muted =! muted;
135         #ifdef LIBVLC_FOUND
136         libvlc_audio_set_mute(player, muted);
137         #else
138         player->setMuted(!muted);
139         #endif
140         emit update();
141     }
142 }
143 
streamUrl(const QString & url)144 void HttpStream::streamUrl(const QString &url)
145 {
146     DBUG << url;
147     #ifdef LIBVLC_FOUND
148     if (player) {
149         libvlc_media_player_stop(player);
150         libvlc_media_player_release(player);
151         libvlc_release(instance);
152         player = 0;
153     }
154     #else
155     if (player) {
156         QMediaContent media = player->media();
157         if (media != nullptr && media.canonicalUrl() != url) {
158             player->stop();
159             player->deleteLater();
160             player = nullptr;
161         }
162     }
163     #endif
164     QUrl qUrl(url);
165     if (!url.isEmpty() && qUrl.isValid() && qUrl.scheme().startsWith("http") && !player) {
166         #ifdef LIBVLC_FOUND
167         instance = libvlc_new(0, NULL);
168         QByteArray byteArrayUrl = url.toUtf8();
169         media = libvlc_media_new_location(instance, byteArrayUrl.constData());
170         player = libvlc_media_player_new_from_media(media);
171         libvlc_media_release(media);
172         #else
173         player = new QMediaPlayer(this);
174         player->setMedia(qUrl);
175         connect(player, &QMediaPlayer::bufferStatusChanged, this, &HttpStream::bufferingProgress);
176         #endif
177         muted = false;
178         setVolume(Configuration(metaObject()->className()).get("volume", currentVolume));
179     }
180     if (player) {
181         state = 0xFFFF; // Force an update...
182         updateStatus();
183     } else {
184         state = MPDState_Inactive;
185     }
186     emit update();
187 }
188 
189 #ifndef LIBVLC_FOUND
bufferingProgress(int progress)190 void HttpStream::bufferingProgress(int progress)
191 {
192     MPDStatus * const status = MPDStatus::self();
193     if (status->state() == MPDState_Playing) {
194         if (progress == 100) {
195             player->play();
196         } else {
197             player->pause();
198         }
199     }
200 }
201 #endif
202 
updateStatus()203 void HttpStream::updateStatus()
204 {
205     if (!player) {
206         return;
207     }
208 
209     MPDStatus * const status = MPDStatus::self();
210     DBUG << status->state() << state;
211 
212     // evaluates to true when it is needed to start or restart media player
213     bool playerNeedsToStart = status->state() == MPDState_Playing;
214     #ifdef LIBVLC_FOUND
215     playerNeedsToStart = playerNeedsToStart && libvlc_media_player_get_state(player) != libvlc_Playing;
216     #else
217     playerNeedsToStart = playerNeedsToStart && player->state() == QMediaPlayer::StoppedState;
218     #endif
219 
220     if (status->state() == state && !playerNeedsToStart) {
221         return;
222     }
223 
224     state = status->state();
225     switch (status->state()) {
226     case MPDState_Playing:
227         // Only start playback if not aready playing
228         if (playerNeedsToStart) {
229         #ifdef LIBVLC_FOUND
230             libvlc_media_player_play(player);
231             startTimer();
232         #else
233             QUrl url = player->media().canonicalUrl();
234             player->setMedia(url);
235         #endif
236         }
237         break;
238     case MPDState_Paused:
239     case MPDState_Inactive:
240     case MPDState_Stopped:
241         #ifdef LIBVLC_FOUND
242         libvlc_media_player_stop(player);
243         stopTimer();
244         #else
245         player->stop();
246         #endif
247         break;
248     default:
249         #ifdef LIBVLC_FOUND
250         stopTimer();
251         #endif
252         break;
253     }
254 }
255 
checkPlayer()256 void HttpStream::checkPlayer()
257 {
258     #ifdef LIBVLC_FOUND
259     if (0 == --playStateChecks) {
260         stopTimer();
261         DBUG << "Max checks reached";
262     }
263     if (libvlc_media_player_get_state(player) == libvlc_Playing) {
264         DBUG << "Playing";
265         stopTimer();
266     } else {
267         DBUG << "Try again";
268         libvlc_media_player_play(player);
269     }
270     #endif
271 }
272 
startTimer()273 void HttpStream::startTimer()
274 {
275     if (!playStateCheckTimer) {
276         playStateCheckTimer = new QTimer(this);
277         playStateCheckTimer->setSingleShot(false);
278         playStateCheckTimer->setInterval(constPlayerCheckPeriod);
279         connect(playStateCheckTimer, SIGNAL(timeout()), SLOT(checkPlayer()));
280     }
281     playStateChecks = constMaxPlayStateChecks;
282     DBUG << playStateChecks;
283     playStateCheckTimer->start();
284 }
285 
stopTimer()286 void HttpStream::stopTimer()
287 {
288     if (playStateCheckTimer) {
289         DBUG;
290         playStateCheckTimer->stop();
291     }
292     playStateChecks = 0;
293 }
294 
295 #include "moc_httpstream.cpp"
296