mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-14 00:04:13 +09:00
UxPlay 1.71: add support for HLS streaming video
This commit is contained in:
305
lib/airplay_video.c
Normal file
305
lib/airplay_video.c
Normal file
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* Copyright (c) 2024 fduncanh
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
// it should only start and stop the media_data_store that handles all HLS transactions, without
|
||||
// otherwise participating in them.
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "raop.h"
|
||||
#include "airplay_video.h"
|
||||
|
||||
struct media_item_s {
|
||||
char *uri;
|
||||
char *playlist;
|
||||
int access;
|
||||
};
|
||||
|
||||
struct airplay_video_s {
|
||||
raop_t *raop;
|
||||
char apple_session_id[37];
|
||||
char playback_uuid[37];
|
||||
char *uri_prefix;
|
||||
char local_uri_prefix[23];
|
||||
int next_uri;
|
||||
int FCUP_RequestID;
|
||||
float start_position_seconds;
|
||||
playback_info_t *playback_info;
|
||||
// The local port of the airplay server on the AirPlay server
|
||||
unsigned short airplay_port;
|
||||
char *master_uri;
|
||||
char *master_playlist;
|
||||
media_item_t *media_data_store;
|
||||
int num_uri;
|
||||
};
|
||||
|
||||
// initialize airplay_video service.
|
||||
int airplay_video_service_init(raop_t *raop, unsigned short http_port,
|
||||
const char *session_id) {
|
||||
char uri[] = "http://localhost:xxxxx";
|
||||
assert(raop);
|
||||
|
||||
airplay_video_t *airplay_video = deregister_airplay_video(raop);
|
||||
if (airplay_video) {
|
||||
airplay_video_service_destroy(airplay_video);
|
||||
}
|
||||
|
||||
airplay_video = (airplay_video_t *) calloc(1, sizeof(airplay_video_t));
|
||||
if (!airplay_video) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* create local_uri_prefix string */
|
||||
strncpy(airplay_video->local_uri_prefix, uri, sizeof(airplay_video->local_uri_prefix));
|
||||
char *ptr = strstr(airplay_video->local_uri_prefix, "xxxxx");
|
||||
snprintf(ptr, 6, "%-5u", http_port);
|
||||
ptr = strstr(airplay_video->local_uri_prefix, " ");
|
||||
if (ptr) {
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
if (!register_airplay_video(raop, airplay_video)) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
printf(" %p %p\n", airplay_video, get_airplay_video(raop));
|
||||
|
||||
airplay_video->raop = raop;
|
||||
|
||||
|
||||
airplay_video->FCUP_RequestID = 0;
|
||||
|
||||
|
||||
size_t len = strlen(session_id);
|
||||
assert(len == 36);
|
||||
strncpy(airplay_video->apple_session_id, session_id, len);
|
||||
(airplay_video->apple_session_id)[len] = '\0';
|
||||
|
||||
airplay_video->start_position_seconds = 0.0f;
|
||||
|
||||
airplay_video->master_uri = NULL;
|
||||
airplay_video->media_data_store = NULL;
|
||||
airplay_video->master_playlist = NULL;
|
||||
airplay_video->num_uri = 0;
|
||||
airplay_video->next_uri = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// destroy the airplay_video service
|
||||
void
|
||||
airplay_video_service_destroy(airplay_video_t *airplay_video)
|
||||
{
|
||||
|
||||
if (airplay_video->uri_prefix) {
|
||||
free(airplay_video->uri_prefix);
|
||||
}
|
||||
if (airplay_video->master_uri) {
|
||||
free (airplay_video->master_uri);
|
||||
}
|
||||
if (airplay_video->media_data_store) {
|
||||
destroy_media_data_store(airplay_video);
|
||||
}
|
||||
if (airplay_video->master_playlist) {
|
||||
free (airplay_video->master_playlist);
|
||||
}
|
||||
|
||||
|
||||
free (airplay_video);
|
||||
}
|
||||
|
||||
const char *get_apple_session_id(airplay_video_t *airplay_video) {
|
||||
return airplay_video->apple_session_id;
|
||||
}
|
||||
|
||||
float get_start_position_seconds(airplay_video_t *airplay_video) {
|
||||
return airplay_video->start_position_seconds;
|
||||
}
|
||||
|
||||
void set_start_position_seconds(airplay_video_t *airplay_video, float start_position_seconds) {
|
||||
airplay_video->start_position_seconds = start_position_seconds;
|
||||
}
|
||||
|
||||
void set_playback_uuid(airplay_video_t *airplay_video, const char *playback_uuid) {
|
||||
size_t len = strlen(playback_uuid);
|
||||
assert(len == 36);
|
||||
memcpy(airplay_video->playback_uuid, playback_uuid, len);
|
||||
(airplay_video->playback_uuid)[len] = '\0';
|
||||
}
|
||||
|
||||
void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix, int uri_prefix_len) {
|
||||
if (airplay_video->uri_prefix) {
|
||||
free (airplay_video->uri_prefix);
|
||||
}
|
||||
airplay_video->uri_prefix = (char *) calloc(uri_prefix_len + 1, sizeof(char));
|
||||
memcpy(airplay_video->uri_prefix, uri_prefix, uri_prefix_len);
|
||||
}
|
||||
|
||||
char *get_uri_prefix(airplay_video_t *airplay_video) {
|
||||
return airplay_video->uri_prefix;
|
||||
}
|
||||
|
||||
char *get_uri_local_prefix(airplay_video_t *airplay_video) {
|
||||
return airplay_video->local_uri_prefix;
|
||||
}
|
||||
|
||||
|
||||
char *get_master_uri(airplay_video_t *airplay_video) {
|
||||
return airplay_video->master_uri;
|
||||
}
|
||||
|
||||
|
||||
int get_next_FCUP_RequestID(airplay_video_t *airplay_video) {
|
||||
return ++(airplay_video->FCUP_RequestID);
|
||||
}
|
||||
|
||||
void set_next_media_uri_id(airplay_video_t *airplay_video, int num) {
|
||||
airplay_video->next_uri = num;
|
||||
}
|
||||
|
||||
int get_next_media_uri_id(airplay_video_t *airplay_video) {
|
||||
return airplay_video->next_uri;
|
||||
}
|
||||
|
||||
|
||||
/* master playlist */
|
||||
|
||||
void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist) {
|
||||
if (airplay_video->master_playlist) {
|
||||
free (airplay_video->master_playlist);
|
||||
}
|
||||
airplay_video->master_playlist = master_playlist;
|
||||
}
|
||||
|
||||
char *get_master_playlist(airplay_video_t *airplay_video) {
|
||||
return airplay_video->master_playlist;
|
||||
}
|
||||
|
||||
/* media_data_store */
|
||||
|
||||
int get_num_media_uri(airplay_video_t *airplay_video) {
|
||||
return airplay_video->num_uri;
|
||||
}
|
||||
|
||||
void destroy_media_data_store(airplay_video_t *airplay_video) {
|
||||
media_item_t *media_data_store = airplay_video->media_data_store;
|
||||
if (media_data_store) {
|
||||
for (int i = 0; i < airplay_video->num_uri ; i ++ ) {
|
||||
if (media_data_store[i].uri) {
|
||||
free (media_data_store[i].uri);
|
||||
}
|
||||
if (media_data_store[i].playlist) {
|
||||
free (media_data_store[i].playlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
free (media_data_store);
|
||||
airplay_video->num_uri = 0;
|
||||
}
|
||||
|
||||
void create_media_data_store(airplay_video_t * airplay_video, char ** uri_list, int num_uri) {
|
||||
destroy_media_data_store(airplay_video);
|
||||
media_item_t *media_data_store = calloc(num_uri, sizeof(media_item_t));
|
||||
for (int i = 0; i < num_uri; i++) {
|
||||
media_data_store[i].uri = uri_list[i];
|
||||
media_data_store[i].playlist = NULL;
|
||||
media_data_store[i].access = 0;
|
||||
}
|
||||
airplay_video->media_data_store = media_data_store;
|
||||
airplay_video->num_uri = num_uri;
|
||||
}
|
||||
|
||||
int store_media_data_playlist_by_num(airplay_video_t *airplay_video, char * media_playlist, int num) {
|
||||
media_item_t *media_data_store = airplay_video->media_data_store;
|
||||
if ( num < 0 || num >= airplay_video->num_uri) {
|
||||
return -1;
|
||||
} else if (media_data_store[num].playlist) {
|
||||
return -2;
|
||||
}
|
||||
media_data_store[num].playlist = media_playlist;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char * get_media_playlist_by_num(airplay_video_t *airplay_video, int num) {
|
||||
media_item_t *media_data_store = airplay_video->media_data_store;
|
||||
if (media_data_store == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (num >= 0 && num <airplay_video->num_uri) {
|
||||
return media_data_store[num].playlist;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char * get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri) {
|
||||
/* Problem: there can be more than one StreamInf playlist with the same uri:
|
||||
* they differ by choice of partner Media (audio, subtitles) playlists
|
||||
* If the same uri is requested again, one of the other ones will be returned
|
||||
* (the least-previously-requested one will be served up)
|
||||
*/
|
||||
media_item_t *media_data_store = airplay_video->media_data_store;
|
||||
if (media_data_store == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
int found = 0;;
|
||||
int num = -1;
|
||||
int access = -1;
|
||||
for (int i = 0; i < airplay_video->num_uri; i++) {
|
||||
if (strstr(media_data_store[i].uri, uri)) {
|
||||
if (!found) {
|
||||
found = 1;
|
||||
num = i;
|
||||
access = media_data_store[i].access;
|
||||
} else {
|
||||
/* change > below to >= to reverse the order of choice */
|
||||
if (access > media_data_store[i].access) {
|
||||
access = media_data_store[i].access;
|
||||
num = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
printf("found %s\n", media_data_store[num].uri);
|
||||
++media_data_store[num].access;
|
||||
return media_data_store[num].playlist;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char * get_media_uri_by_num(airplay_video_t *airplay_video, int num) {
|
||||
media_item_t * media_data_store = airplay_video->media_data_store;
|
||||
if (media_data_store == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (num >= 0 && num < airplay_video->num_uri) {
|
||||
return media_data_store[num].uri;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int get_media_uri_num(airplay_video_t *airplay_video, char * uri) {
|
||||
media_item_t *media_data_store = airplay_video->media_data_store;
|
||||
for (int i = 0; i < airplay_video->num_uri ; i++) {
|
||||
if (strstr(media_data_store[i].uri, uri)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
74
lib/airplay_video.h
Normal file
74
lib/airplay_video.h
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2024 fduncanh, All Rights Reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
*=================================================================
|
||||
*/
|
||||
|
||||
#ifndef AIRPLAY_VIDEO_H
|
||||
#define AIRPLAY_VIDEO_H
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "raop.h"
|
||||
#include "logger.h"
|
||||
|
||||
typedef struct airplay_video_s airplay_video_t;
|
||||
typedef struct media_item_s media_item_t;
|
||||
|
||||
const char *get_apple_session_id(airplay_video_t *airplay_video);
|
||||
void set_start_position_seconds(airplay_video_t *airplay_video, float start_position_seconds);
|
||||
float get_start_position_seconds(airplay_video_t *airplay_video);
|
||||
void set_playback_uuid(airplay_video_t *airplay_video, const char *playback_uuid);
|
||||
void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix, int uri_prefix_len);
|
||||
char *get_uri_prefix(airplay_video_t *airplay_video);
|
||||
char *get_uri_local_prefix(airplay_video_t *airplay_video);
|
||||
int get_next_FCUP_RequestID(airplay_video_t *airplay_video);
|
||||
void set_next_media_uri_id(airplay_video_t *airplay_video, int id);
|
||||
int get_next_media_uri_id(airplay_video_t *airplay_video);
|
||||
char * get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri);
|
||||
void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist);
|
||||
char *get_master_playlist(airplay_video_t *airplay_video);
|
||||
int get_num_media_uri(airplay_video_t *airplay_video);
|
||||
void destroy_media_data_store(airplay_video_t *airplay_video);
|
||||
void create_media_data_store(airplay_video_t * airplay_video, char ** media_data_store, int num_uri);
|
||||
int store_media_data_playlist_by_num(airplay_video_t *airplay_video, char * media_playlist, int num);
|
||||
char *get_media_playlist_by_num(airplay_video_t *airplay_video, int num);
|
||||
char *get_media_uri_by_num(airplay_video_t *airplay_video, int num);
|
||||
int get_media_uri_num(airplay_video_t *airplay_video, char * uri);
|
||||
|
||||
|
||||
void airplay_video_service_destroy(airplay_video_t *airplay_video);
|
||||
|
||||
// C wrappers for c++ class MediaDataStore
|
||||
//create the media_data_store, return a pointer to it.
|
||||
void* media_data_store_create(void *conn_opaque, uint16_t port);
|
||||
|
||||
//delete the media_data_store
|
||||
void media_data_store_destroy(void *media_data_store);
|
||||
|
||||
// called by the POST /action handler:
|
||||
char *process_media_data(void *media_data_store, const char *url, const char *data, int datalen);
|
||||
|
||||
//called by the POST /play handler
|
||||
bool request_media_data(void *media_data_store, const char *primary_url, const char * session_id);
|
||||
|
||||
//called by airplay_video_media_http_connection::get_handler: &path = req.uri)
|
||||
char *query_media_data(void *media_data_store, const char *url, int *len);
|
||||
|
||||
//called by the post_stop_handler:
|
||||
void media_data_store_reset(void *media_data_store);
|
||||
|
||||
const char *adjust_primary_uri(void *media_data_store, const char *url);
|
||||
|
||||
#endif //AIRPLAY_VIDEO_H
|
||||
112
lib/fcup_request.h
Normal file
112
lib/fcup_request.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2022 fduncanh
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
/* this file is part of raop.c via http_handlers.h and should not be included in any other file */
|
||||
|
||||
|
||||
//produces the fcup request plist in xml format as a null-terminated string
|
||||
char *create_fcup_request(const char *url, int request_id, const char *client_session_id, int *datalen) {
|
||||
char *plist_xml = NULL;
|
||||
/* values taken from apsdk-public; */
|
||||
/* these seem to be arbitrary choices */
|
||||
const int sessionID = 1;
|
||||
const int FCUP_Response_ClientInfo = 1;
|
||||
const int FCUP_Response_ClientRef = 40030004;
|
||||
|
||||
/* taken from a working AppleTV? */
|
||||
const char User_Agent[] = "AppleCoreMedia/1.0.0.11B554a (Apple TV; U; CPU OS 7_0_4 like Mac OS X; en_us";
|
||||
|
||||
plist_t req_root_node = plist_new_dict();
|
||||
|
||||
plist_t session_id_node = plist_new_uint((int64_t) sessionID);
|
||||
plist_dict_set_item(req_root_node, "sessionID", session_id_node);
|
||||
plist_t type_node = plist_new_string("unhandledURLRequest");
|
||||
plist_dict_set_item(req_root_node, "type", type_node);
|
||||
|
||||
plist_t fcup_request_node = plist_new_dict();
|
||||
|
||||
plist_t client_info_node = plist_new_uint(FCUP_Response_ClientInfo);
|
||||
plist_dict_set_item(fcup_request_node, "FCUP_Response_ClientInfo", client_info_node);
|
||||
plist_t client_ref_node = plist_new_uint((int64_t) FCUP_Response_ClientRef);
|
||||
plist_dict_set_item(fcup_request_node, "FCUP_Response_ClientRef", client_ref_node);
|
||||
plist_t request_id_node = plist_new_uint((int64_t) request_id);
|
||||
plist_dict_set_item(fcup_request_node, "FCUP_Response_RequestID", request_id_node);
|
||||
plist_t url_node = plist_new_string(url);
|
||||
plist_dict_set_item(fcup_request_node, "FCUP_Response_URL", url_node);
|
||||
plist_t session_id1_node = plist_new_uint((int64_t) sessionID);
|
||||
plist_dict_set_item(fcup_request_node, "sessionID", session_id1_node);
|
||||
|
||||
plist_t fcup_response_header_node = plist_new_dict();
|
||||
plist_t playback_session_id_node = plist_new_string(client_session_id);
|
||||
plist_dict_set_item(fcup_response_header_node, "X-Playback-Session-Id", playback_session_id_node);
|
||||
plist_t user_agent_node = plist_new_string(User_Agent);
|
||||
plist_dict_set_item(fcup_response_header_node, "User-Agent", user_agent_node);
|
||||
|
||||
plist_dict_set_item(fcup_request_node, "FCUP_Response_Headers", fcup_response_header_node);
|
||||
plist_dict_set_item(req_root_node, "request", fcup_request_node);
|
||||
|
||||
uint32_t uint_val;
|
||||
|
||||
plist_to_xml(req_root_node, &plist_xml, &uint_val);
|
||||
*datalen = (int) uint_val;
|
||||
plist_free(req_root_node);
|
||||
assert(plist_xml[*datalen] == '\0');
|
||||
return plist_xml; //needs to be freed after use
|
||||
}
|
||||
|
||||
int fcup_request(void *conn_opaque, const char *media_url, const char *client_session_id, int request_id) {
|
||||
|
||||
raop_conn_t *conn = (raop_conn_t *) conn_opaque;
|
||||
int datalen = 0;
|
||||
int requestlen;
|
||||
|
||||
int socket_fd = httpd_get_connection_socket_by_type(conn->raop->httpd, CONNECTION_TYPE_PTTH, 1);
|
||||
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "fcup_request send socket = %d", socket_fd);
|
||||
|
||||
/* create xml plist request data */
|
||||
char *plist_xml = create_fcup_request(media_url, request_id, client_session_id, &datalen);
|
||||
|
||||
/* use http_response tools for creating the reverse http request */
|
||||
http_response_t *request = http_response_create();
|
||||
http_response_reverse_request_init(request, "POST", "/event", "HTTP/1.1");
|
||||
http_response_add_header(request, "X-Apple-Session-ID", client_session_id);
|
||||
http_response_add_header(request, "Content-Type", "text/x-apple-plist+xml");
|
||||
http_response_finish(request, plist_xml, datalen);
|
||||
|
||||
free(plist_xml);
|
||||
|
||||
const char *http_request = http_response_get_data(request, &requestlen);
|
||||
int send_len = send(socket_fd, http_request, requestlen, 0);
|
||||
if (send_len < 0) {
|
||||
int sock_err = SOCKET_GET_ERROR();
|
||||
logger_log(conn->raop->logger, LOGGER_ERR, "fcup_request: send error %d:%s\n",
|
||||
sock_err, strerror(sock_err));
|
||||
http_response_destroy(request);
|
||||
/* shut down connection? */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG) {
|
||||
char *request_str = utils_data_to_text(http_request, requestlen);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s", request_str);
|
||||
free (request_str);
|
||||
}
|
||||
http_response_destroy(request);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG,"fcup_request: send sent Request of %d bytes from socket %d\n",
|
||||
send_len, socket_fd);
|
||||
return 0;
|
||||
}
|
||||
1001
lib/http_handlers.h
Normal file
1001
lib/http_handlers.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@ struct http_request_s {
|
||||
llhttp_t parser;
|
||||
llhttp_settings_t parser_settings;
|
||||
|
||||
bool is_reverse; // if true, this is a reverse-response from client
|
||||
const char *method;
|
||||
char *url;
|
||||
char protocol[9];
|
||||
@@ -160,7 +161,7 @@ http_request_init(void)
|
||||
|
||||
llhttp_init(&request->parser, HTTP_REQUEST, &request->parser_settings);
|
||||
request->parser.data = request;
|
||||
|
||||
request->is_reverse = false;
|
||||
return request;
|
||||
}
|
||||
|
||||
@@ -206,6 +207,9 @@ int
|
||||
http_request_has_error(http_request_t *request)
|
||||
{
|
||||
assert(request);
|
||||
if (request->is_reverse) {
|
||||
return 0;
|
||||
}
|
||||
return (llhttp_get_errno(&request->parser) != HPE_OK);
|
||||
}
|
||||
|
||||
@@ -213,6 +217,9 @@ const char *
|
||||
http_request_get_error_name(http_request_t *request)
|
||||
{
|
||||
assert(request);
|
||||
if (request->is_reverse) {
|
||||
return NULL;
|
||||
}
|
||||
return llhttp_errno_name(llhttp_get_errno(&request->parser));
|
||||
}
|
||||
|
||||
@@ -220,6 +227,9 @@ const char *
|
||||
http_request_get_error_description(http_request_t *request)
|
||||
{
|
||||
assert(request);
|
||||
if (request->is_reverse) {
|
||||
return NULL;
|
||||
}
|
||||
return llhttp_get_error_reason(&request->parser);
|
||||
}
|
||||
|
||||
@@ -227,6 +237,9 @@ const char *
|
||||
http_request_get_method(http_request_t *request)
|
||||
{
|
||||
assert(request);
|
||||
if (request->is_reverse) {
|
||||
return NULL;
|
||||
}
|
||||
return request->method;
|
||||
}
|
||||
|
||||
@@ -234,6 +247,9 @@ const char *
|
||||
http_request_get_url(http_request_t *request)
|
||||
{
|
||||
assert(request);
|
||||
if (request->is_reverse) {
|
||||
return NULL;
|
||||
}
|
||||
return request->url;
|
||||
}
|
||||
|
||||
@@ -241,6 +257,9 @@ const char *
|
||||
http_request_get_protocol(http_request_t *request)
|
||||
{
|
||||
assert(request);
|
||||
if (request->is_reverse) {
|
||||
return NULL;
|
||||
}
|
||||
return request->protocol;
|
||||
}
|
||||
|
||||
@@ -250,6 +269,9 @@ http_request_get_header(http_request_t *request, const char *name)
|
||||
int i;
|
||||
|
||||
assert(request);
|
||||
if (request->is_reverse) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (i=0; i<request->headers_size; i+=2) {
|
||||
if (!strcmp(request->headers[i], name)) {
|
||||
@@ -263,7 +285,6 @@ const char *
|
||||
http_request_get_data(http_request_t *request, int *datalen)
|
||||
{
|
||||
assert(request);
|
||||
|
||||
if (datalen) {
|
||||
*datalen = request->datalen;
|
||||
}
|
||||
@@ -277,6 +298,10 @@ http_request_get_header_string(http_request_t *request, char **header_str)
|
||||
*header_str = NULL;
|
||||
return 0;
|
||||
}
|
||||
if (request->is_reverse) {
|
||||
*header_str = NULL;
|
||||
return 0;
|
||||
}
|
||||
int len = 0;
|
||||
for (int i = 0; i < request->headers_size; i++) {
|
||||
len += strlen(request->headers[i]);
|
||||
@@ -309,3 +334,11 @@ http_request_get_header_string(http_request_t *request, char **header_str)
|
||||
assert(p == &(str[len]));
|
||||
return len;
|
||||
}
|
||||
|
||||
bool http_request_is_reverse(http_request_t *request) {
|
||||
return request->is_reverse;
|
||||
}
|
||||
|
||||
void http_request_set_reverse(http_request_t *request) {
|
||||
request->is_reverse = true;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
#ifndef HTTP_REQUEST_H
|
||||
#define HTTP_REQUEST_H
|
||||
|
||||
typedef struct http_request_s http_request_t;
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct http_request_s http_request_t;
|
||||
|
||||
http_request_t *http_request_init(void);
|
||||
|
||||
@@ -32,6 +33,8 @@ const char *http_request_get_protocol(http_request_t *request);
|
||||
const char *http_request_get_header(http_request_t *request, const char *name);
|
||||
const char *http_request_get_data(http_request_t *request, int *datalen);
|
||||
int http_request_get_header_string(http_request_t *request, char **header_str);
|
||||
bool http_request_is_reverse(http_request_t *request);
|
||||
void http_request_set_reverse(http_request_t *request);
|
||||
|
||||
void http_request_destroy(http_request_t *request);
|
||||
|
||||
|
||||
@@ -91,6 +91,21 @@ http_response_init(http_response_t *response, const char *protocol, int code, co
|
||||
http_response_add_data(response, "\r\n", 2);
|
||||
}
|
||||
|
||||
void
|
||||
http_response_reverse_request_init(http_response_t *request, const char *method, const char *url, const char *protocol)
|
||||
{
|
||||
assert(request);
|
||||
request->data_length = 0; /* reinitialize a previously-initialized response as a reverse-HTTP (PTTH/1.0) request */
|
||||
|
||||
/* Add first line of response to the data array */
|
||||
http_response_add_data(request, method, strlen(method));
|
||||
http_response_add_data(request, " ", 1);
|
||||
http_response_add_data(request, url, strlen(url));
|
||||
http_response_add_data(request, " ", 1);
|
||||
http_response_add_data(request, protocol, strlen(protocol));
|
||||
http_response_add_data(request, "\r\n", 2);
|
||||
}
|
||||
|
||||
void
|
||||
http_response_destroy(http_response_t *response)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,8 @@ typedef struct http_response_s http_response_t;
|
||||
|
||||
http_response_t *http_response_create();
|
||||
void http_response_init(http_response_t *response, const char *protocol, int code, const char *message);
|
||||
void http_response_reverse_request_init(http_response_t *request, const char *method, const char *url,
|
||||
const char *protocol);
|
||||
|
||||
void http_response_add_header(http_response_t *response, const char *name, const char *value);
|
||||
void http_response_finish(http_response_t *response, const char *data, int datalen);
|
||||
|
||||
167
lib/httpd.c
167
lib/httpd.c
@@ -20,12 +20,24 @@
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "httpd.h"
|
||||
#include "netutils.h"
|
||||
#include "http_request.h"
|
||||
#include "compat.h"
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
static const char *typename[] = {
|
||||
[CONNECTION_TYPE_UNKNOWN] = "Unknown",
|
||||
[CONNECTION_TYPE_RAOP] = "RAOP",
|
||||
[CONNECTION_TYPE_AIRPLAY] = "AirPlay",
|
||||
[CONNECTION_TYPE_PTTH] = "AirPlay (reversed)",
|
||||
[CONNECTION_TYPE_HLS] = "HLS"
|
||||
};
|
||||
|
||||
|
||||
struct http_connection_s {
|
||||
int connected;
|
||||
@@ -57,6 +69,20 @@ struct httpd_s {
|
||||
int server_fd6;
|
||||
};
|
||||
|
||||
int
|
||||
httpd_get_connection_socket (httpd_t *httpd, void *user_data) {
|
||||
for (int i = 0; i < httpd->max_connections; i++) {
|
||||
http_connection_t *connection = &httpd->connections[i];
|
||||
if (!connection->connected) {
|
||||
continue;
|
||||
}
|
||||
if (connection->user_data == user_data) {
|
||||
return connection->socket_fd;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
httpd_set_connection_type (httpd_t *httpd, void *user_data, connection_type_t type) {
|
||||
for (int i = 0; i < httpd->max_connections; i++) {
|
||||
@@ -87,6 +113,42 @@ httpd_count_connection_type (httpd_t *httpd, connection_type_t type) {
|
||||
return count;
|
||||
}
|
||||
|
||||
int
|
||||
httpd_get_connection_socket_by_type (httpd_t *httpd, connection_type_t type, int instance){
|
||||
int count = 0;
|
||||
for (int i = 0; i < httpd->max_connections; i++) {
|
||||
http_connection_t *connection = &httpd->connections[i];
|
||||
if (!connection->connected) {
|
||||
continue;
|
||||
}
|
||||
if (connection->type == type) {
|
||||
count++;
|
||||
if (count == instance) {
|
||||
return connection->socket_fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *
|
||||
httpd_get_connection_by_type (httpd_t *httpd, connection_type_t type, int instance){
|
||||
int count = 0;
|
||||
for (int i = 0; i < httpd->max_connections; i++) {
|
||||
http_connection_t *connection = &httpd->connections[i];
|
||||
if (!connection->connected) {
|
||||
continue;
|
||||
}
|
||||
if (connection->type == type) {
|
||||
count++;
|
||||
if (count == instance) {
|
||||
return connection->user_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define MAX_CONNECTIONS 12 /* value used in AppleTV 3*/
|
||||
httpd_t *
|
||||
httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold)
|
||||
@@ -101,7 +163,6 @@ httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
httpd->nohold = (nohold ? 1 : 0);
|
||||
httpd->max_connections = MAX_CONNECTIONS;
|
||||
httpd->connections = calloc(httpd->max_connections, sizeof(http_connection_t));
|
||||
@@ -213,7 +274,7 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
|
||||
local = netutils_get_address(&local_saddr, &local_len, &local_zone_id);
|
||||
remote = netutils_get_address(&remote_saddr, &remote_len, &remote_zone_id);
|
||||
assert (local_zone_id == remote_zone_id);
|
||||
|
||||
|
||||
ret = httpd_add_connection(httpd, fd, local, local_len, remote, remote_len, local_zone_id);
|
||||
if (ret == -1) {
|
||||
shutdown(fd, SHUT_RDWR);
|
||||
@@ -235,7 +296,7 @@ httpd_remove_known_connections(httpd_t *httpd) {
|
||||
if (!connection->connected || connection->type == CONNECTION_TYPE_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
httpd_remove_connection(httpd, connection);
|
||||
httpd_remove_connection(httpd, connection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,10 +304,11 @@ static THREAD_RETVAL
|
||||
httpd_thread(void *arg)
|
||||
{
|
||||
httpd_t *httpd = arg;
|
||||
char http[] = "HTTP/1.1";
|
||||
char buffer[1024];
|
||||
int i;
|
||||
|
||||
bool logger_debug = (logger_get_level(httpd->logger) >= LOGGER_DEBUG);
|
||||
|
||||
assert(httpd);
|
||||
|
||||
while (1) {
|
||||
@@ -254,6 +316,7 @@ httpd_thread(void *arg)
|
||||
struct timeval tv;
|
||||
int nfds=0;
|
||||
int ret;
|
||||
int new_request;
|
||||
|
||||
MUTEX_LOCK(httpd->run_mutex);
|
||||
if (!httpd->running) {
|
||||
@@ -299,7 +362,7 @@ httpd_thread(void *arg)
|
||||
/* Timeout happened */
|
||||
continue;
|
||||
} else if (ret == -1) {
|
||||
logger_log(httpd->logger, LOGGER_ERR, "httpd error in select");
|
||||
logger_log(httpd->logger, LOGGER_ERR, "httpd error in select: %d %s", errno, strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -337,20 +400,93 @@ httpd_thread(void *arg)
|
||||
if (!connection->request) {
|
||||
connection->request = http_request_init();
|
||||
assert(connection->request);
|
||||
}
|
||||
new_request = 1;
|
||||
if (connection->type == CONNECTION_TYPE_PTTH) {
|
||||
http_request_is_reverse(connection->request);
|
||||
}
|
||||
logger_log(httpd->logger, LOGGER_DEBUG, "new request, connection %d, socket %d type %s",
|
||||
i, connection->socket_fd, typename [connection->type]);
|
||||
} else {
|
||||
new_request = 0;
|
||||
}
|
||||
|
||||
logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d, connection %d", connection->socket_fd, i);
|
||||
ret = recv(connection->socket_fd, buffer, sizeof(buffer), 0);
|
||||
if (ret == 0) {
|
||||
logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd);
|
||||
httpd_remove_connection(httpd, connection);
|
||||
logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d, connection %d",
|
||||
connection->socket_fd, i);
|
||||
if (logger_debug) {
|
||||
printf("\nhttpd: current connections:\n");
|
||||
for (int i = 0; i < httpd->max_connections; i++) {
|
||||
http_connection_t *connection = &httpd->connections[i];
|
||||
if(!connection->connected) {
|
||||
continue;
|
||||
}
|
||||
if (!FD_ISSET(connection->socket_fd, &rfds)) {
|
||||
printf("connection %d type %d socket %d conn %p %s\n", i,
|
||||
connection->type, connection->socket_fd,
|
||||
connection->user_data, typename [connection->type]);
|
||||
} else {
|
||||
printf("connection %d type %d socket %d conn %p %s ACTIVE CONNECTION\n", i, connection->type,
|
||||
connection->socket_fd, connection->user_data, typename [connection->type]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
/* reverse-http responses from the client must not be sent to the llhttp parser:
|
||||
* such messages start with "HTTP/1.1" */
|
||||
if (new_request) {
|
||||
int readstart = 0;
|
||||
new_request = 0;
|
||||
while (readstart < 8) {
|
||||
ret = recv(connection->socket_fd, buffer + readstart, sizeof(buffer) - 1 - readstart, 0);
|
||||
if (ret == 0) {
|
||||
logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d",
|
||||
connection->socket_fd);
|
||||
break;
|
||||
} else if (ret == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
continue;
|
||||
} else {
|
||||
int sock_err = SOCKET_GET_ERROR();
|
||||
logger_log(httpd->logger, LOGGER_ERR, "httpd: recv socket error %d:%s",
|
||||
sock_err, strerror(sock_err));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
readstart += ret;
|
||||
ret = readstart;
|
||||
}
|
||||
}
|
||||
if (!memcmp(buffer, http, 8)) {
|
||||
http_request_set_reverse(connection->request);
|
||||
}
|
||||
} else {
|
||||
ret = recv(connection->socket_fd, buffer, sizeof(buffer) - 1, 0);
|
||||
if (ret == 0) {
|
||||
logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d",
|
||||
connection->socket_fd);
|
||||
httpd_remove_connection(httpd, connection);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (http_request_is_reverse(connection->request)) {
|
||||
/* this is a response from the client to a
|
||||
* GET /event reverse HTTP request from the server */
|
||||
if (ret && logger_debug) {
|
||||
buffer[ret] = '\0';
|
||||
logger_log(httpd->logger, LOGGER_INFO, "<<<< received response from client"
|
||||
" (reversed HTTP = \"PTTH/1.0\") connection"
|
||||
" on socket %d:\n%s\n", connection->socket_fd, buffer);
|
||||
}
|
||||
if (ret == 0) {
|
||||
httpd_remove_connection(httpd, connection);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Parse HTTP request from data read from connection */
|
||||
http_request_add_data(connection->request, buffer, ret);
|
||||
if (http_request_has_error(connection->request)) {
|
||||
logger_log(httpd->logger, LOGGER_ERR, "httpd error in parsing: %s", http_request_get_error_name(connection->request));
|
||||
logger_log(httpd->logger, LOGGER_ERR, "httpd error in parsing: %s",
|
||||
http_request_get_error_name(connection->request));
|
||||
httpd_remove_connection(httpd, connection);
|
||||
continue;
|
||||
}
|
||||
@@ -359,12 +495,13 @@ httpd_thread(void *arg)
|
||||
if (http_request_is_complete(connection->request)) {
|
||||
http_response_t *response = NULL;
|
||||
// Callback the received data to raop
|
||||
if (logger_debug) {
|
||||
if (logger_debug) {
|
||||
const char *method = http_request_get_method(connection->request);
|
||||
const char *url = http_request_get_url(connection->request);
|
||||
const char *protocol = http_request_get_protocol(connection->request);
|
||||
logger_log(httpd->logger, LOGGER_INFO, "httpd request received on socket %d, connection %d, "
|
||||
"method = %s, url = %s, protocol = %s", connection->socket_fd, i, method, url, protocol);
|
||||
logger_log(httpd->logger, LOGGER_INFO, "httpd request received on socket %d, "
|
||||
"connection %d, method = %s, url = %s, protocol = %s",
|
||||
connection->socket_fd, i, method, url, protocol);
|
||||
}
|
||||
httpd->callbacks.conn_request(connection->user_data, connection->request, &response);
|
||||
http_request_destroy(connection->request);
|
||||
|
||||
@@ -23,7 +23,10 @@ typedef struct httpd_s httpd_t;
|
||||
|
||||
typedef enum connectype_type_e {
|
||||
CONNECTION_TYPE_UNKNOWN,
|
||||
CONNECTION_TYPE_RAOP
|
||||
CONNECTION_TYPE_RAOP,
|
||||
CONNECTION_TYPE_AIRPLAY,
|
||||
CONNECTION_TYPE_PTTH,
|
||||
CONNECTION_TYPE_HLS
|
||||
} connection_type_t;
|
||||
|
||||
struct httpd_callbacks_s {
|
||||
@@ -39,7 +42,9 @@ void httpd_remove_known_connections(httpd_t *httpd);
|
||||
|
||||
int httpd_set_connection_type (httpd_t *http, void *user_data, connection_type_t type);
|
||||
int httpd_count_connection_type (httpd_t *http, connection_type_t type);
|
||||
|
||||
int httpd_get_connection_socket (httpd_t *httpd, void *user_data);
|
||||
int httpd_get_connection_socket_by_type (httpd_t *httpd, connection_type_t type, int instance);
|
||||
void *httpd_get_connection_by_type (httpd_t *httpd, connection_type_t type, int instance);
|
||||
httpd_t *httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold);
|
||||
|
||||
int httpd_is_running(httpd_t *httpd);
|
||||
|
||||
332
lib/raop.c
332
lib/raop.c
@@ -72,6 +72,12 @@ struct raop_s {
|
||||
|
||||
/* public key as string */
|
||||
char pk_str[2*ED25519_KEY_SIZE + 1];
|
||||
|
||||
/* place to store media_data_store */
|
||||
airplay_video_t *airplay_video;
|
||||
|
||||
/* activate support for HLS live streaming */
|
||||
bool hls_support;
|
||||
};
|
||||
|
||||
struct raop_conn_s {
|
||||
@@ -81,7 +87,8 @@ struct raop_conn_s {
|
||||
raop_rtp_mirror_t *raop_rtp_mirror;
|
||||
fairplay_t *fairplay;
|
||||
pairing_session_t *session;
|
||||
|
||||
airplay_video_t *airplay_video;
|
||||
|
||||
unsigned char *local;
|
||||
int locallen;
|
||||
|
||||
@@ -92,11 +99,14 @@ struct raop_conn_s {
|
||||
|
||||
connection_type_t connection_type;
|
||||
|
||||
char *client_session_id;
|
||||
|
||||
bool have_active_remote;
|
||||
};
|
||||
typedef struct raop_conn_s raop_conn_t;
|
||||
|
||||
#include "raop_handlers.h"
|
||||
#include "http_handlers.h"
|
||||
|
||||
static void *
|
||||
conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen, unsigned int zone_id) {
|
||||
@@ -147,6 +157,9 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
|
||||
conn->remotelen = remotelen;
|
||||
|
||||
conn->connection_type = CONNECTION_TYPE_UNKNOWN;
|
||||
conn->client_session_id = NULL;
|
||||
conn->airplay_video = NULL;
|
||||
|
||||
|
||||
conn->have_active_remote = false;
|
||||
|
||||
@@ -162,35 +175,110 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
|
||||
char *response_data = NULL;
|
||||
int response_datalen = 0;
|
||||
raop_conn_t *conn = ptr;
|
||||
|
||||
bool hls_request = false;
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request");
|
||||
bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG);
|
||||
|
||||
/*
|
||||
All requests arriving here have been parsed by llhttp to obtain
|
||||
method | url | protocol (RTSP/1.0 or HTTP/1.1)
|
||||
|
||||
There are three types of connections supplying these requests:
|
||||
Connections from the AirPlay client:
|
||||
(1) type RAOP connections with CSeq seqence header, and no X-Apple-Session-ID header
|
||||
(2) type AIRPLAY connection with an X-Apple-Sequence-ID header and no Cseq header
|
||||
Connections from localhost:
|
||||
(3) type HLS internal connections from the local HLS server (gstreamer) at localhost with neither
|
||||
of these headers, but a Host: localhost:[port] header. method = GET.
|
||||
*/
|
||||
|
||||
const char *method = http_request_get_method(request);
|
||||
const char *url = http_request_get_url(request);
|
||||
const char *protocol = http_request_get_protocol(request);
|
||||
|
||||
if (!method) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* this rejects messages from _airplay._tcp for video streaming protocol unless bool raop->hls_support is true*/
|
||||
const char *cseq = http_request_get_header(request, "CSeq");
|
||||
const char *protocol = http_request_get_protocol(request);
|
||||
if (!cseq && !conn->raop->hls_support) {
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "ignoring AirPlay video streaming request (use option -hls to activate HLS support)");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *url = http_request_get_url(request);
|
||||
const char *client_session_id = http_request_get_header(request, "X-Apple-Session-ID");
|
||||
const char *host = http_request_get_header(request, "Host");
|
||||
hls_request = (host && !cseq && !client_session_id);
|
||||
|
||||
if (conn->connection_type == CONNECTION_TYPE_UNKNOWN) {
|
||||
if (httpd_count_connection_type(conn->raop->httpd, CONNECTION_TYPE_RAOP)) {
|
||||
char ipaddr[40];
|
||||
utils_ipaddress_to_string(conn->remotelen, conn->remote, conn->zone_id, ipaddr, (int) (sizeof(ipaddr)));
|
||||
if (httpd_nohold(conn->raop->httpd)) {
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "\"nohold\" feature: switch to new connection request from %s", ipaddr);
|
||||
if (conn->raop->callbacks.video_reset) {
|
||||
printf("**************************video_reset*************************\n");
|
||||
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls);
|
||||
}
|
||||
httpd_remove_known_connections(conn->raop->httpd);
|
||||
if (cseq) {
|
||||
if (httpd_count_connection_type(conn->raop->httpd, CONNECTION_TYPE_RAOP)) {
|
||||
char ipaddr[40];
|
||||
utils_ipaddress_to_string(conn->remotelen, conn->remote, conn->zone_id, ipaddr, (int) (sizeof(ipaddr)));
|
||||
if (httpd_nohold(conn->raop->httpd)) {
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "\"nohold\" feature: switch to new connection request from %s", ipaddr);
|
||||
if (conn->raop->callbacks.video_reset) {
|
||||
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls);
|
||||
}
|
||||
httpd_remove_known_connections(conn->raop->httpd);
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr);
|
||||
*response = http_response_create();
|
||||
http_response_init(*response, protocol, 409, "Conflict: Server is connected to another client");
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "New connection %p identified as Connection type RAOP", ptr);
|
||||
httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_RAOP);
|
||||
conn->connection_type = CONNECTION_TYPE_RAOP;
|
||||
} else if (client_session_id) {
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "New connection %p identified as Connection type AirPlay", ptr);
|
||||
httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_AIRPLAY);
|
||||
conn->connection_type = CONNECTION_TYPE_AIRPLAY;
|
||||
size_t len = strlen(client_session_id) + 1;
|
||||
conn->client_session_id = (char *) malloc(len);
|
||||
strncpy(conn->client_session_id, client_session_id, len);
|
||||
/* airplay video has been requested: shut down any running RAOP udp services */
|
||||
raop_conn_t *raop_conn = (raop_conn_t *) httpd_get_connection_by_type(conn->raop->httpd, CONNECTION_TYPE_RAOP, 1);
|
||||
if (raop_conn) {
|
||||
raop_rtp_mirror_t *raop_rtp_mirror = raop_conn->raop_rtp_mirror;
|
||||
if (raop_rtp_mirror) {
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "New AirPlay connection: stopping RAOP mirror"
|
||||
" service on RAOP connection %p", raop_conn);
|
||||
raop_rtp_mirror_stop(raop_rtp_mirror);
|
||||
}
|
||||
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr);
|
||||
*response = http_response_create();
|
||||
http_response_init(*response, protocol, 409, "Conflict: Server is connected to another client");
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_RAOP);
|
||||
conn->connection_type = CONNECTION_TYPE_RAOP;
|
||||
raop_rtp_t *raop_rtp = raop_conn->raop_rtp;
|
||||
if (raop_rtp) {
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "New AirPlay connection: stopping RAOP audio"
|
||||
" service on RAOP connection %p", raop_conn);
|
||||
raop_rtp_stop(raop_rtp);
|
||||
}
|
||||
|
||||
raop_ntp_t *raop_ntp = raop_conn->raop_ntp;
|
||||
if (raop_rtp) {
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "New AirPlay connection: stopping NTP time"
|
||||
" service on RAOP connection %p", raop_conn);
|
||||
raop_ntp_stop(raop_ntp);
|
||||
}
|
||||
}
|
||||
} else if (host) {
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "New connection %p identified as Connection type HLS", ptr);
|
||||
httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_HLS);
|
||||
conn->connection_type = CONNECTION_TYPE_HLS;
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_WARNING, "connection from unknown connection type");
|
||||
}
|
||||
}
|
||||
|
||||
/* this response code and message will be modified by the handler if necessary */
|
||||
*response = http_response_create();
|
||||
http_response_init(*response, protocol, 200, "OK");
|
||||
|
||||
/* is this really necessary? or is it obsolete? (added for all RTSP requests EXCEPT "RECORD") */
|
||||
if (cseq && strcmp(method, "RECORD")) {
|
||||
http_response_add_header(*response, "Audio-Jack-Status", "connected; type=digital");
|
||||
}
|
||||
|
||||
if (!conn->have_active_remote) {
|
||||
@@ -204,15 +292,6 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!method) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* this rejects unsupported messages from _airplay._tcp for video streaming protocol*/
|
||||
if (!cseq) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s %s %s", method, url, protocol);
|
||||
char *header_str= NULL;
|
||||
http_request_get_header_string(request, &header_str);
|
||||
@@ -225,74 +304,125 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
|
||||
const char *request_data = http_request_get_data(request, &request_datalen);
|
||||
if (request_data && logger_debug) {
|
||||
if (request_datalen > 0) {
|
||||
/* logger has a buffer limit of 4096 */
|
||||
if (data_is_plist) {
|
||||
plist_t req_root_node = NULL;
|
||||
plist_t req_root_node = NULL;
|
||||
plist_from_bin(request_data, request_datalen, &req_root_node);
|
||||
char * plist_xml;
|
||||
uint32_t plist_len;
|
||||
plist_to_xml(req_root_node, &plist_xml, &plist_len);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
|
||||
printf("%s\n",plist_xml);
|
||||
//logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
|
||||
free(plist_xml);
|
||||
plist_free(req_root_node);
|
||||
} else if (data_is_text) {
|
||||
char *data_str = utils_data_to_text((char *) request_data, request_datalen);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
printf("%s\n", data_str);
|
||||
//logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
free(data_str);
|
||||
} else {
|
||||
char *data_str = utils_data_to_string((unsigned char *) request_data, request_datalen, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
printf("%s\n", data_str);
|
||||
//logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
free(data_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*response = http_response_create();
|
||||
http_response_init(*response, protocol, 200, "OK");
|
||||
|
||||
//http_response_add_header(*response, "Apple-Jack-Status", "connected; type=analog");
|
||||
|
||||
if (client_session_id) {
|
||||
assert(!strcmp(client_session_id, conn->client_session_id));
|
||||
}
|
||||
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "Handling request %s with URL %s", method, url);
|
||||
raop_handler_t handler = NULL;
|
||||
if (!strcmp(method, "GET") && !strcmp(url, "/info")) {
|
||||
handler = &raop_handler_info;
|
||||
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-pin-start")) {
|
||||
handler = &raop_handler_pairpinstart;
|
||||
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup-pin")) {
|
||||
handler = &raop_handler_pairsetup_pin;
|
||||
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup")) {
|
||||
handler = &raop_handler_pairsetup;
|
||||
} else if (!strcmp(method, "POST") && !strcmp(url, "/pair-verify")) {
|
||||
handler = &raop_handler_pairverify;
|
||||
} else if (!strcmp(method, "POST") && !strcmp(url, "/fp-setup")) {
|
||||
handler = &raop_handler_fpsetup;
|
||||
} else if (!strcmp(method, "OPTIONS")) {
|
||||
handler = &raop_handler_options;
|
||||
} else if (!strcmp(method, "SETUP")) {
|
||||
handler = &raop_handler_setup;
|
||||
} else if (!strcmp(method, "GET_PARAMETER")) {
|
||||
handler = &raop_handler_get_parameter;
|
||||
} else if (!strcmp(method, "SET_PARAMETER")) {
|
||||
handler = &raop_handler_set_parameter;
|
||||
} else if (!strcmp(method, "POST") && !strcmp(url, "/feedback")) {
|
||||
handler = &raop_handler_feedback;
|
||||
} else if (!strcmp(method, "RECORD")) {
|
||||
handler = &raop_handler_record;
|
||||
} else if (!strcmp(method, "FLUSH")) {
|
||||
handler = &raop_handler_flush;
|
||||
} else if (!strcmp(method, "TEARDOWN")) {
|
||||
handler = &raop_handler_teardown;
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "Unhandled Client Request: %s %s", method, url);
|
||||
if (!hls_request && !strcmp(protocol, "RTSP/1.0")) {
|
||||
if (!strcmp(method, "POST")) {
|
||||
if (!strcmp(url, "/feedback")) {
|
||||
handler = &raop_handler_feedback;
|
||||
} else if (!strcmp(url, "/pair-pin-start")) {
|
||||
handler = &raop_handler_pairpinstart;
|
||||
} else if (!strcmp(url, "/pair-setup-pin")) {
|
||||
handler = &raop_handler_pairsetup_pin;
|
||||
} else if (!strcmp(url, "/pair-setup")) {
|
||||
handler = &raop_handler_pairsetup;
|
||||
} else if (!strcmp(url, "/pair-verify")) {
|
||||
handler = &raop_handler_pairverify;
|
||||
} else if (!strcmp(url, "/fp-setup")) {
|
||||
handler = &raop_handler_fpsetup;
|
||||
} else if (!strcmp(url, "/getProperty")) {
|
||||
handler = &http_handler_get_property;
|
||||
} else if (!strcmp(url, "/audioMode")) {
|
||||
//handler = &http_handler_audioMode;
|
||||
}
|
||||
} else if (!strcmp(method, "GET")) {
|
||||
if (!strcmp(url, "/info")) {
|
||||
handler = &raop_handler_info;
|
||||
}
|
||||
} else if (!strcmp(method, "OPTIONS")) {
|
||||
handler = &raop_handler_options;
|
||||
} else if (!strcmp(method, "SETUP")) {
|
||||
handler = &raop_handler_setup;
|
||||
} else if (!strcmp(method, "GET_PARAMETER")) {
|
||||
handler = &raop_handler_get_parameter;
|
||||
} else if (!strcmp(method, "SET_PARAMETER")) {
|
||||
handler = &raop_handler_set_parameter;
|
||||
} else if (!strcmp(method, "RECORD")) {
|
||||
handler = &raop_handler_record;
|
||||
} else if (!strcmp(method, "FLUSH")) {
|
||||
handler = &raop_handler_flush;
|
||||
} else if (!strcmp(method, "TEARDOWN")) {
|
||||
handler = &raop_handler_teardown;
|
||||
}
|
||||
} else if (!hls_request && !strcmp(protocol, "HTTP/1.1")) {
|
||||
if (!strcmp(method, "POST")) {
|
||||
if (!strcmp(url, "/reverse")) {
|
||||
handler = &http_handler_reverse;
|
||||
} else if (!strcmp(url, "/play")) {
|
||||
handler = &http_handler_play;
|
||||
} else if (!strncmp (url, "/getProperty?", strlen("/getProperty?"))) {
|
||||
handler = &http_handler_get_property;
|
||||
} else if (!strncmp(url, "/scrub?", strlen("/scrub?"))) {
|
||||
handler = &http_handler_scrub;
|
||||
} else if (!strncmp(url, "/rate?", strlen("/rate?"))) {
|
||||
handler = &http_handler_rate;
|
||||
} else if (!strcmp(url, "/stop")) {
|
||||
handler = &http_handler_stop;
|
||||
} else if (!strcmp(url, "/action")) {
|
||||
handler = &http_handler_action;
|
||||
} else if (!strcmp(url, "/fp-setup2")) {
|
||||
handler = &http_handler_fpsetup2;
|
||||
}
|
||||
} else if (!strcmp(method, "GET")) {
|
||||
if (!strcmp(url, "/server-info")) {
|
||||
handler = &http_handler_server_info;
|
||||
} else if (!strcmp(url, "/playback-info")) {
|
||||
handler = &http_handler_playback_info;
|
||||
}
|
||||
} else if (!strcmp(method, "PUT")) {
|
||||
if (!strncmp (url, "/setProperty?", strlen("/setProperty?"))) {
|
||||
handler = &http_handler_set_property;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
} else if (hls_request) {
|
||||
handler = &http_handler_hls;
|
||||
}
|
||||
|
||||
if (handler != NULL) {
|
||||
handler(conn, request, *response, &response_data, &response_datalen);
|
||||
} else {
|
||||
logger_log(conn->raop->logger, LOGGER_INFO,
|
||||
"Unhandled Client Request: %s %s %s", method, url, protocol);
|
||||
}
|
||||
|
||||
finish:;
|
||||
http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION);
|
||||
http_response_add_header(*response, "CSeq", cseq);
|
||||
if (!hls_request) {
|
||||
http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION);
|
||||
if (cseq) {
|
||||
http_response_add_header(*response, "CSeq", cseq);
|
||||
}
|
||||
}
|
||||
http_response_finish(*response, response_data, response_datalen);
|
||||
|
||||
int len;
|
||||
@@ -304,11 +434,14 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
|
||||
}
|
||||
header_str = utils_data_to_text(data, len);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s", header_str);
|
||||
|
||||
bool data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL);
|
||||
bool data_is_text = (strstr(header_str,"text/parameters") != NULL);
|
||||
bool data_is_text = (strstr(header_str,"text/") != NULL ||
|
||||
strstr(header_str, "x-mpegURL") != NULL);
|
||||
free(header_str);
|
||||
if (response_data) {
|
||||
if (response_datalen > 0 && logger_debug) {
|
||||
/* logger has a buffer limit of 4096 */
|
||||
if (data_is_plist) {
|
||||
plist_t res_root_node = NULL;
|
||||
plist_from_bin(response_data, response_datalen, &res_root_node);
|
||||
@@ -316,21 +449,24 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
|
||||
uint32_t plist_len;
|
||||
plist_to_xml(res_root_node, &plist_xml, &plist_len);
|
||||
plist_free(res_root_node);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
|
||||
printf("%s\n", plist_xml);
|
||||
//logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
|
||||
free(plist_xml);
|
||||
} else if (data_is_text) {
|
||||
char *data_str = utils_data_to_text((char*) response_data, response_datalen);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
printf("%s\n", data_str);
|
||||
//logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
free(data_str);
|
||||
} else {
|
||||
char *data_str = utils_data_to_string((unsigned char *) response_data, response_datalen, 16);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
printf("%s\n", data_str);
|
||||
//logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
|
||||
free(data_str);
|
||||
}
|
||||
}
|
||||
free(response_data);
|
||||
response_data = NULL;
|
||||
response_datalen = 0;
|
||||
if (response_data) {
|
||||
free(response_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +500,13 @@ conn_destroy(void *ptr) {
|
||||
free(conn->remote);
|
||||
pairing_session_destroy(conn->session);
|
||||
fairplay_destroy(conn->fairplay);
|
||||
if (conn->client_session_id) {
|
||||
free(conn->client_session_id);
|
||||
}
|
||||
if (conn->airplay_video) {
|
||||
airplay_video_service_destroy(conn->airplay_video);
|
||||
}
|
||||
|
||||
free(conn);
|
||||
}
|
||||
|
||||
@@ -420,6 +563,8 @@ raop_init(raop_callbacks_t *callbacks) {
|
||||
raop->max_ntp_timeouts = 0;
|
||||
raop->audio_delay_micros = 250000;
|
||||
|
||||
raop->hls_support = false;
|
||||
|
||||
return raop;
|
||||
}
|
||||
|
||||
@@ -474,6 +619,7 @@ raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile)
|
||||
void
|
||||
raop_destroy(raop_t *raop) {
|
||||
if (raop) {
|
||||
raop_destroy_airplay_video(raop);
|
||||
raop_stop(raop);
|
||||
pairing_destroy(raop->pairing);
|
||||
httpd_destroy(raop->httpd);
|
||||
@@ -533,6 +679,8 @@ int raop_set_plist(raop_t *raop, const char *plist_item, const int value) {
|
||||
} else if (strcmp(plist_item, "pin") == 0) {
|
||||
raop->pin = value;
|
||||
raop->use_pin = true;
|
||||
} else if (strcmp(plist_item, "hls") == 0) {
|
||||
raop->hls_support = (value > 0 ? true : false);
|
||||
} else {
|
||||
retval = -1;
|
||||
}
|
||||
@@ -604,3 +752,27 @@ void raop_remove_known_connections(raop_t * raop) {
|
||||
httpd_remove_known_connections(raop->httpd);
|
||||
}
|
||||
|
||||
airplay_video_t *deregister_airplay_video(raop_t *raop) {
|
||||
airplay_video_t *airplay_video = raop->airplay_video;
|
||||
raop->airplay_video = NULL;
|
||||
return airplay_video;
|
||||
}
|
||||
|
||||
bool register_airplay_video(raop_t *raop, airplay_video_t *airplay_video) {
|
||||
if (raop->airplay_video) {
|
||||
return false;
|
||||
}
|
||||
raop->airplay_video = airplay_video;
|
||||
return true;
|
||||
}
|
||||
|
||||
airplay_video_t * get_airplay_video(raop_t *raop) {
|
||||
return raop->airplay_video;
|
||||
}
|
||||
|
||||
void raop_destroy_airplay_video(raop_t *raop) {
|
||||
if (raop->airplay_video) {
|
||||
airplay_video_service_destroy(raop->airplay_video);
|
||||
raop->airplay_video = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
40
lib/raop.h
40
lib/raop.h
@@ -21,6 +21,7 @@
|
||||
#include "dnssd.h"
|
||||
#include "stream.h"
|
||||
#include "raop_ntp.h"
|
||||
#include "airplay_video.h"
|
||||
|
||||
#if defined (WIN32) && defined(DLL_EXPORT)
|
||||
# define RAOP_API __declspec(dllexport)
|
||||
@@ -36,12 +37,29 @@ typedef struct raop_s raop_t;
|
||||
|
||||
typedef void (*raop_log_callback_t)(void *cls, int level, const char *msg);
|
||||
|
||||
|
||||
typedef struct playback_info_s {
|
||||
//char * uuid;
|
||||
uint32_t stallcount;
|
||||
double duration;
|
||||
double position;
|
||||
float rate;
|
||||
bool ready_to_play;
|
||||
bool playback_buffer_empty;
|
||||
bool playback_buffer_full;
|
||||
bool playback_likely_to_keep_up;
|
||||
int num_loaded_time_ranges;
|
||||
int num_seekable_time_ranges;
|
||||
void *loadedTimeRanges;
|
||||
void *seekableTimeRanges;
|
||||
} playback_info_t;
|
||||
|
||||
typedef enum video_codec_e {
|
||||
VIDEO_CODEC_UNKNOWN,
|
||||
VIDEO_CODEC_H264,
|
||||
VIDEO_CODEC_H265
|
||||
} video_codec_t;
|
||||
|
||||
|
||||
struct raop_callbacks_s {
|
||||
void* cls;
|
||||
|
||||
@@ -49,8 +67,7 @@ struct raop_callbacks_s {
|
||||
void (*video_process)(void *cls, raop_ntp_t *ntp, video_decode_struct *data);
|
||||
void (*video_pause)(void *cls);
|
||||
void (*video_resume)(void *cls);
|
||||
void (*video_codec) (void *cls, video_codec_t video_codec);
|
||||
|
||||
|
||||
/* Optional but recommended callback functions */
|
||||
void (*conn_init)(void *cls);
|
||||
void (*conn_destroy)(void *cls);
|
||||
@@ -72,11 +89,25 @@ struct raop_callbacks_s {
|
||||
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
|
||||
void (*video_reset) (void *cls);
|
||||
void (*video_set_codec)(void *cls, video_codec_t codec);
|
||||
/* for HLS video player controls */
|
||||
void (*on_video_play) (void *cls, const char *location, const float start_position);
|
||||
void (*on_video_scrub) (void *cls, const float position);
|
||||
void (*on_video_rate) (void *cls, const float rate);
|
||||
void (*on_video_stop) (void *cls);
|
||||
void (*on_video_acquire_playback_info) (void *cls, playback_info_t *playback_video);
|
||||
|
||||
};
|
||||
typedef struct raop_callbacks_s raop_callbacks_t;
|
||||
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote,
|
||||
int remote_addr_len, unsigned short timing_rport, timing_protocol_t *time_protocol);
|
||||
int remote_addr_len, unsigned short timing_rport,
|
||||
timing_protocol_t *time_protocol);
|
||||
|
||||
int airplay_video_service_init(raop_t *raop, unsigned short port, const char *session_id);
|
||||
|
||||
bool register_airplay_video(raop_t *raop, airplay_video_t *airplay_video);
|
||||
airplay_video_t *get_airplay_video(raop_t *raop);
|
||||
airplay_video_t *deregister_airplay_video(raop_t *raop);
|
||||
|
||||
RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks);
|
||||
RAOP_API int raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile);
|
||||
RAOP_API void raop_set_log_level(raop_t *raop, int level);
|
||||
@@ -93,6 +124,7 @@ RAOP_API void raop_stop(raop_t *raop);
|
||||
RAOP_API void raop_set_dnssd(raop_t *raop, dnssd_t *dnssd);
|
||||
RAOP_API void raop_destroy(raop_t *raop);
|
||||
RAOP_API void raop_remove_known_connections(raop_t * raop);
|
||||
RAOP_API void raop_destroy_airplay_video(raop_t *raop);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -197,7 +197,6 @@ raop_handler_pairpinstart(raop_conn_t *conn,
|
||||
logger_log(conn->raop->logger, LOGGER_INFO, "*** CLIENT MUST NOW ENTER PIN = \"%s\" AS AIRPLAY PASSWORD", pin);
|
||||
*response_data = NULL;
|
||||
response_datalen = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -749,13 +748,14 @@ raop_handler_setup(raop_conn_t *conn,
|
||||
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks,
|
||||
conn->raop_ntp, remote, conn->remotelen, aeskey);
|
||||
|
||||
// plist_t res_event_port_node = plist_new_uint(conn->raop->port);
|
||||
plist_t res_event_port_node = plist_new_uint(0);
|
||||
/* the event port is not used in mirror mode or audio mode */
|
||||
unsigned short event_port = 0;
|
||||
plist_t res_event_port_node = plist_new_uint(event_port);
|
||||
plist_t res_timing_port_node = plist_new_uint(timing_lport);
|
||||
plist_dict_set_item(res_root_node, "timingPort", res_timing_port_node);
|
||||
plist_dict_set_item(res_root_node, "eventPort", res_event_port_node);
|
||||
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "eport = %d, tport = %d", 0, timing_lport);
|
||||
logger_log(conn->raop->logger, LOGGER_DEBUG, "eport = %d, tport = %d", event_port, timing_lport);
|
||||
}
|
||||
|
||||
// Process stream setup requests
|
||||
|
||||
11
lib/utils.c
11
lib/utils.c
@@ -282,3 +282,14 @@ int utils_ipaddress_to_string(int addresslen, const unsigned char *address, unsi
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const char *gmt_time_string() {
|
||||
static char date_buf[64];
|
||||
memset(date_buf, 0, 64);
|
||||
|
||||
time_t now = time(0);
|
||||
if (strftime(date_buf, 63, "%c GMT", gmtime(&now)))
|
||||
return date_buf;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per
|
||||
char *utils_data_to_text(const char *data, int datalen);
|
||||
void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);
|
||||
void ntp_timestamp_to_seconds(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);
|
||||
const char *gmt_time_string();
|
||||
int utils_ipaddress_to_string(int addresslen, const unsigned char *address,
|
||||
unsigned int zone_id, char *string, int len);
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user