]> git.ipfire.org Git - thirdparty/shairport-sync.git/blob - metadata_hub.c
Update RELEASENOTES-DEVELOPMENT.md
[thirdparty/shairport-sync.git] / metadata_hub.c
1 /*
2 * Metadata hub and access methods.
3 * Basically, if you need to store metadata
4 * (e.g. for use with the dbus interfaces),
5 * then you need a metadata hub,
6 * where everything is stored
7 * This file is part of Shairport Sync.
8 * Copyright (c) Mike Brady 2017--2022
9 * All rights reserved.
10 *
11 * Permission is hereby granted, free of charge, to any person
12 * obtaining a copy of this software and associated documentation
13 * files (the "Software"), to deal in the Software without
14 * restriction, including without limitation the rights to use,
15 * copy, modify, merge, publish, distribute, sublicense, and/or
16 * sell copies of the Software, and to permit persons to whom the
17 * Software is furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be
20 * included in all copies or substantial portions of the Software.
21 *
22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
24 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
26 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29 * OTHER DEALINGS IN THE SOFTWARE.
30 */
31
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <dirent.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <inttypes.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42
43 #include "config.h"
44
45 #include "common.h"
46 #include "dacp.h"
47 #include "metadata_hub.h"
48
49 #ifdef CONFIG_MBEDTLS
50 #include <mbedtls/md5.h>
51 #include <mbedtls/version.h>
52 #endif
53
54 #ifdef CONFIG_POLARSSL
55 #include <polarssl/md5.h>
56 #endif
57
58 #ifdef CONFIG_OPENSSL
59 #include <openssl/evp.h>
60 #endif
61
62 struct metadata_bundle metadata_store;
63
64 int metadata_hub_initialised = 0;
65
66 pthread_rwlock_t metadata_hub_re_lock = PTHREAD_RWLOCK_INITIALIZER;
67
68 int string_update(char **str, int *flag, char *s) {
69 if (s)
70 return string_update_with_size(str, flag, s, strlen(s));
71 else
72 return string_update_with_size(str, flag, NULL, 0);
73 }
74
75 void metadata_hub_init(void) {
76 // debug(1, "Metadata bundle initialisation.");
77 memset(&metadata_store, 0, sizeof(metadata_store));
78 metadata_hub_initialised = 1;
79 }
80
81 void metadata_hub_stop(void) {}
82
83 void add_metadata_watcher(metadata_watcher fn, void *userdata) {
84 int i;
85 for (i = 0; i < number_of_watchers; i++) {
86 if (metadata_store.watchers[i] == NULL) {
87 metadata_store.watchers[i] = fn;
88 metadata_store.watchers_data[i] = userdata;
89 // debug(1, "Added a metadata watcher into slot %d", i);
90 break;
91 }
92 }
93 }
94
95 void run_metadata_watchers(void) {
96 int i;
97 for (i = 0; i < number_of_watchers; i++) {
98 if (metadata_store.watchers[i]) {
99 metadata_store.watchers[i](&metadata_store, metadata_store.watchers_data[i]);
100 }
101 }
102 // turn off changed flags
103 metadata_store.cover_art_pathname_changed = 0;
104 metadata_store.client_ip_changed = 0;
105 metadata_store.client_name_changed = 0;
106 metadata_store.server_ip_changed = 0;
107 metadata_store.progress_string_changed = 0;
108 metadata_store.item_id_changed = 0;
109 metadata_store.item_composite_id_changed = 0;
110 metadata_store.artist_name_changed = 0;
111 metadata_store.album_artist_name_changed = 0;
112 metadata_store.album_name_changed = 0;
113 metadata_store.song_data_kind_changed = 0;
114 metadata_store.track_name_changed = 0;
115 metadata_store.genre_changed = 0;
116 metadata_store.comment_changed = 0;
117 metadata_store.composer_changed = 0;
118 metadata_store.file_kind_changed = 0;
119 metadata_store.song_description_changed = 0;
120 metadata_store.song_album_artist_changed = 0;
121 metadata_store.sort_artist_changed = 0;
122 metadata_store.sort_album_changed = 0;
123 metadata_store.sort_composer_changed = 0;
124 metadata_store.songtime_in_milliseconds_changed = 0;
125 }
126
127 void metadata_hub_unlock_hub_mutex_cleanup(__attribute__((unused)) void *arg) {
128 debug(1, "metadata_hub_unlock_hub_mutex_cleanup called.");
129 metadata_hub_modify_epilog(0);
130 }
131
132 char *last_metadata_hub_modify_prolog_file = NULL;
133 int last_metadata_hub_modify_prolog_line;
134 int metadata_hub_re_lock_access_is_delayed;
135
136 void _metadata_hub_modify_prolog(const char *filename, const int linenumber) {
137 // always run this before changing an entry or a sequence of entries in the metadata_hub
138 // debug(1, "locking metadata hub for writing");
139 if (pthread_rwlock_trywrlock(&metadata_hub_re_lock) != 0) {
140 if (last_metadata_hub_modify_prolog_file)
141 debug(2, "Metadata_hub write lock at \"%s:%d\" is already taken at \"%s:%d\" -- must wait.",
142 filename, linenumber, last_metadata_hub_modify_prolog_file,
143 last_metadata_hub_modify_prolog_line);
144 else
145 debug(2, "Metadata_hub write lock is already taken by unknown -- must wait.");
146 metadata_hub_re_lock_access_is_delayed = 0;
147 pthread_rwlock_wrlock(&metadata_hub_re_lock);
148 debug(2, "Okay -- acquired the metadata_hub write lock at \"%s:%d\".", filename, linenumber);
149 } else {
150 if (last_metadata_hub_modify_prolog_file) {
151 free(last_metadata_hub_modify_prolog_file);
152 }
153 last_metadata_hub_modify_prolog_file = strdup(filename);
154 last_metadata_hub_modify_prolog_line = linenumber;
155 // debug(3, "Metadata_hub write lock acquired.");
156 }
157 metadata_hub_re_lock_access_is_delayed = 0;
158 }
159
160 void _metadata_hub_modify_epilog(int modified, const char *filename, const int linenumber) {
161 metadata_store.dacp_server_has_been_active =
162 metadata_store.dacp_server_active; // set the scanner_has_been_active now.
163 if (modified) {
164 run_metadata_watchers();
165 }
166 if (metadata_hub_re_lock_access_is_delayed) {
167 if (last_metadata_hub_modify_prolog_file) {
168 debug(1, "Metadata_hub write lock taken at \"%s:%d\" is freed at \"%s:%d\".",
169 last_metadata_hub_modify_prolog_file, last_metadata_hub_modify_prolog_line, filename,
170 linenumber);
171 free(last_metadata_hub_modify_prolog_file);
172 last_metadata_hub_modify_prolog_file = NULL;
173 } else {
174 debug(1, "Metadata_hub write lock taken at an unknown place is freed at \"%s:%d\".", filename,
175 linenumber);
176 }
177 }
178 pthread_rwlock_unlock(&metadata_hub_re_lock);
179 // debug(3, "Metadata_hub write lock unlocked.");
180 }
181
182 /*
183 void _metadata_hub_read_prolog(const char *filename, const int linenumber) {
184 // always run this before reading an entry or a sequence of entries in the metadata_hub
185 // debug(1, "locking metadata hub for reading");
186 if (pthread_rwlock_tryrdlock(&metadata_hub_re_lock) != 0) {
187 debug(1, "Metadata_hub read lock is already taken -- must wait.");
188 pthread_rwlock_rdlock(&metadata_hub_re_lock);
189 debug(1, "Okay -- acquired the metadata_hub read lock.");
190 }
191 }
192
193 void _metadata_hub_read_epilog(const char *filename, const int linenumber) {
194 // always run this after reading an entry or a sequence of entries in the metadata_hub
195 // debug(1, "unlocking metadata hub for reading");
196 pthread_rwlock_unlock(&metadata_hub_re_lock);
197 }
198 */
199 char *metadata_write_image_file(const char *buf, int len) {
200
201 // warning -- this removes all files from the directory apart from this one, if it exists
202 // it will return a path to the image file allocated with malloc.
203 // free it if you don't need it.
204
205 char *path = NULL; // this will be what is returned
206 if (strcmp(config.cover_art_cache_dir, "") != 0) { // an empty string means do not write the file
207
208 uint8_t img_md5[16];
209 // uint8_t ap_md5[16];
210
211 #ifdef CONFIG_OPENSSL
212 EVP_MD_CTX *ctx;
213 unsigned int img_md5_len = EVP_MD_size(EVP_md5());
214
215 ctx = EVP_MD_CTX_new();
216 EVP_DigestInit_ex(ctx, EVP_md5(), NULL);
217 EVP_DigestUpdate(ctx, buf, len);
218 EVP_DigestFinal_ex(ctx, img_md5, &img_md5_len);
219 EVP_MD_CTX_free(ctx);
220 #endif
221
222 #ifdef CONFIG_MBEDTLS
223 #if MBEDTLS_VERSION_MINOR >= 7
224 mbedtls_md5_context tctx;
225 mbedtls_md5_starts_ret(&tctx);
226 mbedtls_md5_update_ret(&tctx, (const unsigned char *)buf, len);
227 mbedtls_md5_finish_ret(&tctx, img_md5);
228 #else
229 mbedtls_md5_context tctx;
230 mbedtls_md5_starts(&tctx);
231 mbedtls_md5_update(&tctx, (const unsigned char *)buf, len);
232 mbedtls_md5_finish(&tctx, img_md5);
233 #endif
234 #endif
235
236 #ifdef CONFIG_POLARSSL
237 md5_context tctx;
238 md5_starts(&tctx);
239 md5_update(&tctx, (const unsigned char *)buf, len);
240 md5_finish(&tctx, img_md5);
241 #endif
242
243 char img_md5_str[33];
244 memset(img_md5_str, 0, sizeof(img_md5_str));
245 char *ext;
246 char png[] = "png";
247 char jpg[] = "jpg";
248 int i;
249 for (i = 0; i < 16; i++)
250 snprintf(&img_md5_str[i * 2], 3, "%02x", (uint8_t)img_md5[i]);
251 // see if the file is a jpeg or a png
252 if (strncmp(buf, "\xFF\xD8\xFF", 3) == 0)
253 ext = jpg;
254 else if (strncmp(buf, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8) == 0)
255 ext = png;
256 else {
257 debug(1, "Unidentified image type of cover art -- jpg extension used.");
258 ext = jpg;
259 }
260 mode_t oldumask = umask(000);
261 int result = mkpath(config.cover_art_cache_dir, 0777);
262 umask(oldumask);
263 if ((result == 0) || (result == -EEXIST)) {
264 // see if the file exists by opening it.
265 // if it exists, we're done
266 char *prefix = "cover-";
267
268 size_t pl = strlen(config.cover_art_cache_dir) + 1 + strlen(prefix) + strlen(img_md5_str) +
269 1 + strlen(ext);
270
271 path = malloc(pl + 1);
272 snprintf(path, pl + 1, "%s/%s%s.%s", config.cover_art_cache_dir, prefix, img_md5_str, ext);
273 int cover_fd = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRWXU | S_IRGRP | S_IROTH);
274 if (cover_fd > 0) {
275 // write the contents
276 if (write(cover_fd, buf, len) < len) {
277 warn("Writing cover art file \"%s\" failed!", path);
278 free(path);
279 path = NULL;
280 }
281 close(cover_fd);
282
283 // now delete all other files, if requested
284 if (config.retain_coverart == 0) {
285 DIR *d;
286 struct dirent *dir;
287 d = opendir(config.cover_art_cache_dir);
288 if (d) {
289 int fnl = strlen(prefix) + strlen(img_md5_str) + 1 + strlen(ext) + 1;
290
291 char *full_filename = malloc(fnl);
292 if (full_filename == NULL)
293 die("Can't allocate memory at metadata_write_image_file.");
294 memset(full_filename, 0, fnl);
295 snprintf(full_filename, fnl, "%s%s.%s", prefix, img_md5_str, ext);
296 int dir_fd = open(config.cover_art_cache_dir, O_DIRECTORY);
297 if (dir_fd > 0) {
298 while ((dir = readdir(d)) != NULL) {
299 if (dir->d_type == DT_REG) {
300 if (strcmp(full_filename, dir->d_name) != 0) {
301 if (unlinkat(dir_fd, dir->d_name, 0) != 0) {
302 debug(1, "Error %d deleting cover art file \"%s\".", errno, dir->d_name);
303 }
304 }
305 }
306 }
307 if (close(dir_fd) < 0)
308 debug(1, "Error %d closing directory \"%s\"", errno, config.cover_art_cache_dir);
309 } else {
310 debug(1, "Can't open the directory \"%s\" for deletion -- error %d.",
311 config.cover_art_cache_dir, errno);
312 }
313 free(full_filename);
314 closedir(d);
315 }
316 }
317 } else {
318 // if (errno == EEXIST)
319 // debug(1, "Cover art file \"%s\" already exists!", path);
320 // else {
321 if (errno != EEXIST) {
322 warn("Could not open file \"%s\" for writing cover art", path);
323 free(path);
324 path = NULL;
325 }
326 }
327 } else {
328 debug(1, "Couldn't access or create the cover art cache directory \"%s\".",
329 config.cover_art_cache_dir);
330 }
331 }
332 return path;
333 }
334
335 int metadata_packet_item_changed = 0; // set if any parsed part of a metadata stream changes
336
337 void metadata_hub_process_metadata(uint32_t type, uint32_t code, char *data, uint32_t length) {
338 // metadata coming in from the audio source or from Shairport Sync itself passes through here
339 // this has more information about tags, which might be relevant:
340 // https://code.google.com/p/ytrack/wiki/DMAP
341
342 // Some metadata items are contained in one metadata packet.
343 // The start of the metadata packet is signalled by an 'ssnc' 'mdst' item and
344 // the end of it by an 'ssnc 'mden' item.
345 // We don't set "changed" for them individually; instead we set it when the 'mden' token
346 // comes in if the metadata_packet_item_changed item is set by parsed items
347 // within the packet.
348
349 int changed = 0;
350 metadata_hub_modify_prolog();
351 pthread_cleanup_push(metadata_hub_unlock_hub_mutex_cleanup, NULL);
352
353 char *cs;
354 if (type == 'core') {
355 switch (code) {
356 case 'asdk': {
357 // get the one-byte number as an unsigned number
358 int song_data_kind = data[0]; // one byte
359 song_data_kind = song_data_kind & 0xFF; // unsigned
360 debug(2, "MH Song Data Kind seen: \"%d\" of length %u.", song_data_kind, length);
361 if ((song_data_kind != metadata_store.song_data_kind) ||
362 (metadata_store.song_data_kind_is_valid == 0)) {
363 metadata_store.song_data_kind = song_data_kind;
364 metadata_store.song_data_kind_changed = 1;
365 metadata_store.song_data_kind_is_valid = 1;
366 debug(2, "MH Song Data Kind set to: \"%d\"", metadata_store.song_data_kind);
367 metadata_packet_item_changed = 1;
368 }
369 } break;
370 case 'mper': {
371 // get the 64-bit number as a uint64_t by reading two uint32_t s and combining them
372 uint64_t vl = ntohl(*(uint32_t *)data); // get the high order 32 bits
373 vl = vl << 32; // shift them into the correct location
374 uint64_t ul = ntohl(*(uint32_t *)(data + sizeof(uint32_t))); // and the low order 32 bits
375 vl = vl + ul;
376 debug(2, "MH Item ID seen: \"%" PRIx64 "\" of length %u.", vl, length);
377 if ((vl != metadata_store.item_id) || (metadata_store.item_id_is_valid == 0)) {
378 metadata_store.item_id = vl;
379 metadata_store.item_id_changed = 1;
380 metadata_store.item_id_is_valid = 1;
381 debug(2, "MH Item ID set to: \"%" PRIx64 "\"", metadata_store.item_id);
382 metadata_packet_item_changed = 1;
383 }
384 } break;
385 case 'astm': {
386 uint32_t ui = ntohl(*(uint32_t *)data);
387 debug(2, "MH Song Time seen: \"%u\" of length %u.", ui, length);
388 if ((ui != metadata_store.songtime_in_milliseconds) ||
389 (metadata_store.songtime_in_milliseconds_is_valid == 0)) {
390 metadata_store.songtime_in_milliseconds = ui;
391 metadata_store.songtime_in_milliseconds_changed = 1;
392 metadata_store.songtime_in_milliseconds_is_valid = 1;
393 debug(2, "MH Song Time set to: \"%u\"", metadata_store.songtime_in_milliseconds);
394 metadata_packet_item_changed = 1;
395 }
396 } break;
397 case 'asal':
398 cs = strndup(data, length);
399 if (string_update(&metadata_store.album_name, &metadata_store.album_name_changed, cs)) {
400 debug(2, "MH Album name set to: \"%s\"", metadata_store.album_name);
401 metadata_packet_item_changed = 1;
402 }
403 free(cs);
404 break;
405 case 'asar':
406 cs = strndup(data, length);
407 if (string_update(&metadata_store.artist_name, &metadata_store.artist_name_changed, cs)) {
408 debug(2, "MH Artist name set to: \"%s\"", metadata_store.artist_name);
409 metadata_packet_item_changed = 1;
410 }
411 free(cs);
412 break;
413 case 'assl':
414 cs = strndup(data, length);
415 if (string_update(&metadata_store.album_artist_name,
416 &metadata_store.album_artist_name_changed, cs)) {
417 debug(2, "MH Album Artist name set to: \"%s\"", metadata_store.album_artist_name);
418 metadata_packet_item_changed = 1;
419 }
420 free(cs);
421 break;
422 case 'ascm':
423 cs = strndup(data, length);
424 if (string_update(&metadata_store.comment, &metadata_store.comment_changed, cs)) {
425 debug(2, "MH Comment set to: \"%s\"", metadata_store.comment);
426 metadata_packet_item_changed = 1;
427 }
428 free(cs);
429 break;
430 case 'asgn':
431 cs = strndup(data, length);
432 if (string_update(&metadata_store.genre, &metadata_store.genre_changed, cs)) {
433 debug(2, "MH Genre set to: \"%s\"", metadata_store.genre);
434 metadata_packet_item_changed = 1;
435 }
436 free(cs);
437 break;
438 case 'minm':
439 cs = strndup(data, length);
440 if (string_update(&metadata_store.track_name, &metadata_store.track_name_changed, cs)) {
441 debug(2, "MH Track Name set to: \"%s\"", metadata_store.track_name);
442 metadata_packet_item_changed = 1;
443 }
444 free(cs);
445 break;
446 case 'ascp':
447 cs = strndup(data, length);
448 if (string_update(&metadata_store.composer, &metadata_store.composer_changed, cs)) {
449 debug(2, "MH Composer set to: \"%s\"", metadata_store.composer);
450 metadata_packet_item_changed = 1;
451 }
452 free(cs);
453 break;
454 case 'asdt':
455 cs = strndup(data, length);
456 if (string_update(&metadata_store.song_description, &metadata_store.song_description_changed,
457 cs)) {
458 debug(2, "MH Song Description set to: \"%s\"", metadata_store.song_description);
459 }
460 free(cs);
461 break;
462 case 'asaa':
463 cs = strndup(data, length);
464 if (string_update(&metadata_store.song_album_artist,
465 &metadata_store.song_album_artist_changed, cs)) {
466 debug(2, "MH Song Album Artist set to: \"%s\"", metadata_store.song_album_artist);
467 metadata_packet_item_changed = 1;
468 }
469 free(cs);
470 break;
471 case 'assn':
472 cs = strndup(data, length);
473 if (string_update(&metadata_store.sort_name, &metadata_store.sort_name_changed, cs)) {
474 debug(2, "MH Sort Name set to: \"%s\"", metadata_store.sort_name);
475 metadata_packet_item_changed = 1;
476 }
477 free(cs);
478 break;
479 case 'assa':
480 cs = strndup(data, length);
481 if (string_update(&metadata_store.sort_artist, &metadata_store.sort_artist_changed, cs)) {
482 debug(2, "MH Sort Artist set to: \"%s\"", metadata_store.sort_artist);
483 metadata_packet_item_changed = 1;
484 }
485 free(cs);
486 break;
487 case 'assu':
488 cs = strndup(data, length);
489 if (string_update(&metadata_store.sort_album, &metadata_store.sort_album_changed, cs)) {
490 debug(2, "MH Sort Album set to: \"%s\"", metadata_store.sort_album);
491 metadata_packet_item_changed = 1;
492 }
493 free(cs);
494 break;
495 case 'assc':
496 cs = strndup(data, length);
497 if (string_update(&metadata_store.sort_composer, &metadata_store.sort_composer_changed, cs)) {
498 debug(2, "MH Sort Composer set to: \"%s\"", metadata_store.sort_composer);
499 metadata_packet_item_changed = 1;
500 }
501 free(cs);
502 default:
503 /*
504 {
505 char typestring[5];
506 *(uint32_t *)typestring = htonl(type);
507 typestring[4] = 0;
508 char codestring[5];
509 *(uint32_t *)codestring = htonl(code);
510 codestring[4] = 0;
511 char *payload;
512 if (length < 2048)
513 payload = strndup(data, length);
514 else
515 payload = NULL;
516 debug(1, "MH \"%s\" \"%s\" (%d bytes): \"%s\".", typestring, codestring, length,
517 payload);
518 if (payload)
519 free(payload);
520 }
521 */
522 break;
523 }
524 } else if (type == 'ssnc') {
525 switch (code) {
526 // ignore the following
527 case 'pcst':
528 case 'pcen':
529 break;
530 case 'mdst':
531 debug(2, "MH Metadata stream processing start.");
532 metadata_packet_item_changed = 0;
533 break;
534 case 'mden':
535 if (metadata_packet_item_changed != 0)
536 debug(2, "MH Metadata stream processing end with changes.");
537 else
538 debug(2, "MH Metadata stream processing end without changes.");
539 changed = metadata_packet_item_changed;
540 break;
541 case 'PICT':
542 debug(2, "MH Picture received, length %u bytes.", length);
543
544 char uri[2048];
545 if ((length > 16) &&
546 (strcmp(config.cover_art_cache_dir, "") != 0)) { // if it's okay to write the file
547 // make this uncancellable
548 int oldState;
549 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldState); // make this un-cancellable
550 char *pathname = metadata_write_image_file(data, length);
551 snprintf(uri, sizeof(uri), "file://%s", pathname);
552 free(pathname);
553 pthread_setcancelstate(oldState, NULL);
554
555 } else {
556 uri[0] = '\0';
557 }
558 if (string_update(&metadata_store.cover_art_pathname,
559 &metadata_store.cover_art_pathname_changed,
560 uri)) // if the picture's file path is different from the stored one...
561 changed = 1;
562 else
563 changed = 0;
564 // pthread_cleanup_pop(0); // don't remove the lock -- it'll have been done
565 break;
566 case 'clip':
567 cs = strndup(data, length);
568 if (string_update(&metadata_store.client_ip, &metadata_store.client_ip_changed, cs)) {
569 changed = 1;
570 debug(2, "MH Client IP set to: \"%s\"", metadata_store.client_ip);
571 }
572 free(cs);
573 break;
574 case 'snam':
575 cs = strndup(data, length);
576 if (string_update(&metadata_store.client_name, &metadata_store.client_name_changed, cs)) {
577 changed = 1;
578 debug(2, "MH Client Name set to: \"%s\"", metadata_store.client_name);
579 }
580 free(cs);
581 break;
582 case 'prgr':
583 cs = strndup(data, length);
584 if (string_update(&metadata_store.progress_string, &metadata_store.progress_string_changed,
585 cs)) {
586 changed = 1;
587 debug(2, "MH Progress String set to: \"%s\"", metadata_store.progress_string);
588 }
589 free(cs);
590 break;
591 case 'phbt':
592 cs = strndup(data, length);
593 if (string_update(&metadata_store.frame_position_string,
594 &metadata_store.frame_position_string_changed, cs)) {
595 changed = 1;
596 debug(2, "MH Frame Position String set to: \"%s\"", metadata_store.frame_position_string);
597 }
598 free(cs);
599 break;
600 case 'phb0':
601 cs = strndup(data, length);
602 if (string_update(&metadata_store.first_frame_position_string,
603 &metadata_store.first_frame_position_string_changed, cs)) {
604 changed = 1;
605 debug(2, "MH First Frame Position String set to: \"%s\"",
606 metadata_store.first_frame_position_string);
607 }
608 free(cs);
609 break;
610 case 'styp':
611 cs = strndup(data, length);
612 if (string_update(&metadata_store.stream_type, &metadata_store.stream_type_changed, cs)) {
613 changed = 1;
614 debug(2, "MH Stream Type set to: \"%s\"", metadata_store.stream_type);
615 }
616 free(cs);
617 break;
618 case 'svip':
619 cs = strndup(data, length);
620 if (string_update(&metadata_store.server_ip, &metadata_store.server_ip_changed, cs)) {
621 changed = 1;
622 debug(2, "MH Server IP set to: \"%s\"", metadata_store.server_ip);
623 }
624 free(cs);
625 break;
626 case 'abeg':
627 changed = (metadata_store.active_state != AM_ACTIVE);
628 metadata_store.active_state = AM_ACTIVE;
629 break;
630 case 'aend':
631 changed = (metadata_store.active_state != AM_INACTIVE);
632 metadata_store.active_state = AM_INACTIVE;
633 break;
634 case 'pres':
635 case 'pbeg':
636 changed = ((metadata_store.player_state != PS_PLAYING) ||
637 (metadata_store.player_thread_active == 0));
638 metadata_store.player_state = PS_PLAYING;
639 metadata_store.player_thread_active = 1;
640 break;
641 case 'pend':
642 changed = ((metadata_store.player_state != PS_STOPPED) ||
643 (metadata_store.player_thread_active == 1));
644 metadata_store.player_state = PS_STOPPED;
645 metadata_store.player_thread_active = 0;
646 break;
647 case 'paus':
648 changed = (metadata_store.player_state != PS_PAUSED);
649 metadata_store.player_state = PS_PAUSED;
650 break;
651 /*
652 // not using this anymore.
653 case 'pffr': // this is sent when the first frame has been received
654 case 'prsm':
655 changed = (metadata_store.player_state != PS_PLAYING);
656 metadata_store.player_state = PS_PLAYING;
657 break;
658 */
659 case 'pvol': {
660 // Note: it's assumed that the config.airplay volume has already been correctly set.
661 // int32_t actual_volume;
662 // int gv = dacp_get_volume(&actual_volume);
663 // metadata_hub_modify_prolog();
664 // if ((gv == 200) && (metadata_store.speaker_volume != actual_volume)) {
665 // metadata_store.speaker_volume = actual_volume;
666 // changed = 1;
667 //}
668 if (metadata_store.airplay_volume != config.airplay_volume) {
669 metadata_store.airplay_volume = config.airplay_volume;
670 changed = 1;
671 }
672 } break;
673
674 default: {
675 char typestring[5];
676 uint32_t tm = htonl(type);
677 memcpy(typestring, &tm, sizeof(uint32_t));
678 typestring[4] = 0;
679 char codestring[5];
680 uint32_t cm = htonl(code);
681 memcpy(codestring, &cm, sizeof(uint32_t));
682 codestring[4] = 0;
683 char *payload;
684 if (length < 2048)
685 payload = strndup(data, length);
686 else
687 payload = NULL;
688 // debug(1, "MH \"%s\" \"%s\" (%d bytes): \"%s\".", typestring, codestring, length, payload);
689 if (payload)
690 free(payload);
691 }
692 }
693 }
694 pthread_cleanup_pop(0); // don't remove the lock
695 metadata_hub_modify_epilog(changed);
696 }