UxPlay 1.71: add support for HLS streaming video

This commit is contained in:
F. Duncanh
2024-12-10 01:36:32 -05:00
parent 9aaead3748
commit 08c6f0cdb0
23 changed files with 2559 additions and 361 deletions

305
lib/airplay_video.c Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 "";
}

View File

@@ -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