1 /*
2 * Copyright 2003-2021 The Music Player Daemon Project
3 * http://www.musicpd.org
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 /*
21 * Saving and loading the playlist to/from the state file.
22 *
23 */
24
25 #include "PlaylistState.hxx"
26 #include "PlaylistError.hxx"
27 #include "Playlist.hxx"
28 #include "SingleMode.hxx"
29 #include "StateFileConfig.hxx"
30 #include "queue/QueueSave.hxx"
31 #include "io/LineReader.hxx"
32 #include "io/BufferedOutputStream.hxx"
33 #include "player/Control.hxx"
34 #include "util/CharUtil.hxx"
35 #include "util/StringAPI.hxx"
36 #include "util/StringCompare.hxx"
37 #include "util/NumberParser.hxx"
38 #include "Log.hxx"
39
40 #include <string.h>
41 #include <stdlib.h>
42
43 #define PLAYLIST_STATE_FILE_STATE "state: "
44 #define PLAYLIST_STATE_FILE_RANDOM "random: "
45 #define PLAYLIST_STATE_FILE_REPEAT "repeat: "
46 #define PLAYLIST_STATE_FILE_SINGLE "single: "
47 #define PLAYLIST_STATE_FILE_CONSUME "consume: "
48 #define PLAYLIST_STATE_FILE_CURRENT "current: "
49 #define PLAYLIST_STATE_FILE_TIME "time: "
50 #define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
51 #define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: "
52 #define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: "
53 #define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
54 #define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
55
56 #define PLAYLIST_STATE_FILE_STATE_PLAY "play"
57 #define PLAYLIST_STATE_FILE_STATE_PAUSE "pause"
58 #define PLAYLIST_STATE_FILE_STATE_STOP "stop"
59
60 void
playlist_state_save(BufferedOutputStream & os,const struct playlist & playlist,PlayerControl & pc)61 playlist_state_save(BufferedOutputStream &os, const struct playlist &playlist,
62 PlayerControl &pc)
63 {
64 const auto player_status = pc.LockGetStatus();
65
66 os.Write(PLAYLIST_STATE_FILE_STATE);
67
68 if (playlist.playing) {
69 switch (player_status.state) {
70 case PlayerState::PAUSE:
71 os.Write(PLAYLIST_STATE_FILE_STATE_PAUSE "\n");
72 break;
73 default:
74 os.Write(PLAYLIST_STATE_FILE_STATE_PLAY "\n");
75 }
76 os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n",
77 playlist.queue.OrderToPosition(playlist.current));
78 os.Format(PLAYLIST_STATE_FILE_TIME "%f\n",
79 player_status.elapsed_time.ToDoubleS());
80 } else {
81 os.Write(PLAYLIST_STATE_FILE_STATE_STOP "\n");
82
83 if (playlist.current >= 0)
84 os.Format(PLAYLIST_STATE_FILE_CURRENT "%i\n",
85 playlist.queue.OrderToPosition(playlist.current));
86 }
87
88 os.Format(PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random);
89 os.Format(PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat);
90 os.Format(PLAYLIST_STATE_FILE_SINGLE "%i\n",
91 (int)playlist.queue.single);
92 os.Format(PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist.queue.consume);
93 os.Format(PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
94 (int)pc.GetCrossFade().count());
95 os.Format(PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n",
96 (double)pc.GetMixRampDb());
97 os.Format(PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
98 pc.GetMixRampDelay().count());
99 os.Write(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n");
100 queue_save(os, playlist.queue);
101 os.Write(PLAYLIST_STATE_FILE_PLAYLIST_END "\n");
102 }
103
104 static void
playlist_state_load(LineReader & file,const SongLoader & song_loader,struct playlist & playlist)105 playlist_state_load(LineReader &file, const SongLoader &song_loader,
106 struct playlist &playlist)
107 {
108 const char *line = file.ReadLine();
109 if (line == nullptr) {
110 LogWarning(playlist_domain, "No playlist in state file");
111 return;
112 }
113
114 while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) {
115 queue_load_song(file, song_loader, line, playlist.queue);
116
117 line = file.ReadLine();
118 if (line == nullptr) {
119 LogWarning(playlist_domain,
120 "'" PLAYLIST_STATE_FILE_PLAYLIST_END
121 "' not found in state file");
122 break;
123 }
124 }
125
126 playlist.queue.IncrementVersion();
127 }
128
129 bool
playlist_state_restore(const StateFileConfig & config,const char * line,LineReader & file,const SongLoader & song_loader,struct playlist & playlist,PlayerControl & pc)130 playlist_state_restore(const StateFileConfig &config,
131 const char *line, LineReader &file,
132 const SongLoader &song_loader,
133 struct playlist &playlist, PlayerControl &pc)
134 {
135 int current = -1;
136 SongTime seek_time = SongTime::zero();
137 bool random_mode = false;
138
139 line = StringAfterPrefix(line, PLAYLIST_STATE_FILE_STATE);
140 if (line == nullptr)
141 return false;
142
143 PlayerState state;
144 if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0)
145 state = PlayerState::PLAY;
146 else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0)
147 state = PlayerState::PAUSE;
148 else
149 state = PlayerState::STOP;
150
151 while ((line = file.ReadLine()) != nullptr) {
152 const char *p;
153 if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_TIME))) {
154 seek_time = SongTime::FromS(ParseDouble(p));
155 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_REPEAT))) {
156 playlist.SetRepeat(pc, StringIsEqual(p, "1"));
157 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) {
158 playlist.SetSingle(pc, SingleFromString(p));
159 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CONSUME))) {
160 playlist.SetConsume(StringIsEqual(p, "1"));
161 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) {
162 pc.SetCrossFade(FloatDuration(atoi(p)));
163 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) {
164 pc.SetMixRampDb(ParseFloat(p));
165 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) {
166 /* this check discards "nan" which was used
167 prior to MPD 0.18 */
168 if (IsDigitASCII(*p))
169 pc.SetMixRampDelay(FloatDuration(ParseFloat(p)));
170 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) {
171 random_mode = StringIsEqual(p, "1");
172 } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) {
173 current = atoi(p);
174 } else if (StringStartsWith(line,
175 PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) {
176 playlist_state_load(file, song_loader, playlist);
177 }
178 }
179
180 playlist.SetRandom(pc, random_mode);
181
182 if (!playlist.queue.IsEmpty()) {
183 if (!playlist.queue.IsValidPosition(current))
184 current = 0;
185
186 if (state == PlayerState::PLAY && config.restore_paused)
187 /* the user doesn't want MPD to auto-start
188 playback after startup; fall back to
189 "pause" */
190 state = PlayerState::PAUSE;
191
192 /* enable all devices for the first time; this must be
193 called here, after the audio output states were
194 restored, before playback begins */
195 if (state != PlayerState::STOP)
196 pc.LockUpdateAudio();
197
198 if (state == PlayerState::STOP /* && config_option */)
199 playlist.current = current;
200 else if (seek_time.count() == 0) {
201 try {
202 playlist.PlayPosition(pc, current);
203 } catch (...) {
204 /* TODO: log error? */
205 }
206 } else {
207 try {
208 playlist.SeekSongPosition(pc, current,
209 seek_time);
210 } catch (...) {
211 /* TODO: log error? */
212 }
213 }
214
215 if (state == PlayerState::PAUSE)
216 pc.LockPause();
217 }
218
219 return true;
220 }
221
222 unsigned
playlist_state_get_hash(const playlist & playlist,PlayerControl & pc)223 playlist_state_get_hash(const playlist &playlist,
224 PlayerControl &pc)
225 {
226 const auto player_status = pc.LockGetStatus();
227
228 return playlist.queue.version ^
229 (player_status.state != PlayerState::STOP
230 ? (player_status.elapsed_time.ToS() << 8)
231 : 0) ^
232 (playlist.current >= 0
233 ? (playlist.queue.OrderToPosition(playlist.current) << 16)
234 : 0) ^
235 ((int)pc.GetCrossFade().count() << 20) ^
236 (unsigned(player_status.state) << 24) ^
237 /* note that this takes 2 bits */
238 ((int)playlist.queue.single << 25) ^
239 (playlist.queue.random << 27) ^
240 (playlist.queue.repeat << 28) ^
241 (playlist.queue.consume << 30) ^
242 (playlist.queue.random << 31);
243 }
244