mirror of
https://github.com/owntone/owntone-server.git
synced 2025-04-14 08:16:17 -04:00
Enable streaming of transcoded content
This commit is contained in:
parent
2c2498b6b4
commit
7a601d7daa
255
src/httpd.c
255
src/httpd.c
@ -43,6 +43,7 @@
|
|||||||
#include "httpd.h"
|
#include "httpd.h"
|
||||||
#include "httpd_rsp.h"
|
#include "httpd_rsp.h"
|
||||||
#include "httpd_daap.h"
|
#include "httpd_daap.h"
|
||||||
|
#include "transcode.h"
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -73,9 +74,11 @@ struct stream_chunk {
|
|||||||
struct evbuffer *evbuf;
|
struct evbuffer *evbuf;
|
||||||
int id;
|
int id;
|
||||||
int fd;
|
int fd;
|
||||||
off_t size;
|
size_t size;
|
||||||
off_t offset;
|
size_t offset;
|
||||||
|
size_t start_offset;
|
||||||
int marked;
|
int marked;
|
||||||
|
struct transcode_ctx *xcode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -101,7 +104,84 @@ static pthread_t tid_httpd;
|
|||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
stream_chunk_cb(int fd, short event, void *arg)
|
stream_chunk_xcode_cb(int fd, short event, void *arg)
|
||||||
|
{
|
||||||
|
struct stream_chunk *st;
|
||||||
|
struct timeval tv;
|
||||||
|
int xcoded;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
st = (struct stream_chunk *)arg;
|
||||||
|
|
||||||
|
xcoded = transcode(st->xcode, st->evbuf, STREAM_CHUNK_SIZE);
|
||||||
|
if (xcoded <= 0)
|
||||||
|
{
|
||||||
|
if (xcoded == 0)
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Done streaming transcoded file id %d\n", st->id);
|
||||||
|
else
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Transcoding error, file id %d\n", st->id);
|
||||||
|
|
||||||
|
goto end_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_HTTPD, "Got %d bytes from transcode; streaming file id %d\n", xcoded, st->id);
|
||||||
|
|
||||||
|
/* Consume transcoded data until we meet start_offset */
|
||||||
|
if (st->start_offset > st->offset)
|
||||||
|
{
|
||||||
|
ret = st->start_offset - st->offset;
|
||||||
|
|
||||||
|
if (ret < xcoded)
|
||||||
|
{
|
||||||
|
evbuffer_drain(st->evbuf, ret);
|
||||||
|
st->offset += ret;
|
||||||
|
|
||||||
|
ret = xcoded - ret;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
evbuffer_drain(st->evbuf, xcoded);
|
||||||
|
st->offset += xcoded;
|
||||||
|
|
||||||
|
goto continue_stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ret = xcoded;
|
||||||
|
|
||||||
|
if (ret > 0)
|
||||||
|
evhttp_send_reply_chunk(st->req, st->evbuf);
|
||||||
|
|
||||||
|
st->offset += ret;
|
||||||
|
|
||||||
|
if (!st->marked && (st->offset > ((st->size * 80) / 100)))
|
||||||
|
{
|
||||||
|
st->marked = 1;
|
||||||
|
db_playcount_increment(NULL, st->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue_stream:
|
||||||
|
evutil_timerclear(&tv);
|
||||||
|
ret = event_base_once(evbase_httpd, -1, EV_TIMEOUT, stream_chunk_xcode_cb, st, &tv);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming\n");
|
||||||
|
|
||||||
|
goto end_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
end_stream:
|
||||||
|
evhttp_send_reply_end(st->req);
|
||||||
|
|
||||||
|
evbuffer_free(st->evbuf);
|
||||||
|
transcode_cleanup(st->xcode);
|
||||||
|
free(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
stream_chunk_raw_cb(int fd, short event, void *arg)
|
||||||
{
|
{
|
||||||
struct stream_chunk *st;
|
struct stream_chunk *st;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
@ -110,11 +190,13 @@ stream_chunk_cb(int fd, short event, void *arg)
|
|||||||
st = (struct stream_chunk *)arg;
|
st = (struct stream_chunk *)arg;
|
||||||
|
|
||||||
ret = evbuffer_read(st->evbuf, st->fd, STREAM_CHUNK_SIZE);
|
ret = evbuffer_read(st->evbuf, st->fd, STREAM_CHUNK_SIZE);
|
||||||
if (ret < 0)
|
if (ret <= 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Streaming error, file id %d\n", st->id);
|
if (ret == 0)
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Done streaming file id %d\n", st->id);
|
||||||
|
else
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Streaming error, file id %d\n", st->id);
|
||||||
|
|
||||||
evbuffer_free(st->evbuf);
|
|
||||||
goto end_stream;
|
goto end_stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,15 +213,8 @@ stream_chunk_cb(int fd, short event, void *arg)
|
|||||||
db_playcount_increment(NULL, st->id);
|
db_playcount_increment(NULL, st->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret == 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Done streaming file id %d\n", st->id);
|
|
||||||
|
|
||||||
goto end_stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
evutil_timerclear(&tv);
|
evutil_timerclear(&tv);
|
||||||
ret = event_base_once(evbase_httpd, -1, EV_TIMEOUT, stream_chunk_cb, st, &tv);
|
ret = event_base_once(evbase_httpd, -1, EV_TIMEOUT, stream_chunk_raw_cb, st, &tv);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming\n");
|
DPRINTF(E_LOG, L_HTTPD, "Could not re-add one-shot event for streaming\n");
|
||||||
@ -163,6 +238,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
{
|
{
|
||||||
struct media_file_info *mfi;
|
struct media_file_info *mfi;
|
||||||
struct stream_chunk *st;
|
struct stream_chunk *st;
|
||||||
|
void (*stream_cb)(int fd, short event, void *arg);
|
||||||
struct stat sb;
|
struct stat sb;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
const char *param;
|
const char *param;
|
||||||
@ -171,8 +247,6 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
int transcode;
|
int transcode;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
transcode = 0; /* FIXME: not supported yet */
|
|
||||||
|
|
||||||
offset = 0;
|
offset = 0;
|
||||||
param = evhttp_find_header(req->input_headers, "Range");
|
param = evhttp_find_header(req->input_headers, "Range");
|
||||||
if (param)
|
if (param)
|
||||||
@ -204,19 +278,6 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transcode)
|
|
||||||
{
|
|
||||||
/* Not supported yet */
|
|
||||||
|
|
||||||
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Transcoding not supported");
|
|
||||||
|
|
||||||
db_dispose_item(mfi);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stream the raw file */
|
|
||||||
DPRINTF(E_INF, L_HTTPD, "Preparing to stream %s\n", mfi->path);
|
|
||||||
|
|
||||||
st = (struct stream_chunk *)malloc(sizeof(struct stream_chunk));
|
st = (struct stream_chunk *)malloc(sizeof(struct stream_chunk));
|
||||||
if (!st)
|
if (!st)
|
||||||
{
|
{
|
||||||
@ -227,54 +288,87 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
db_dispose_item(mfi);
|
db_dispose_item(mfi);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(st, 0, sizeof(struct stream_chunk));
|
memset(st, 0, sizeof(struct stream_chunk));
|
||||||
|
|
||||||
st->fd = open(mfi->path, O_RDONLY);
|
transcode = transcode_needed(req->input_headers, mfi->codectype);
|
||||||
if (st->fd < 0)
|
|
||||||
|
if (transcode)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", mfi->path, strerror(errno));
|
DPRINTF(E_INF, L_HTTPD, "Preparing to transcode %s\n", mfi->path);
|
||||||
|
|
||||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
stream_cb = stream_chunk_xcode_cb;
|
||||||
|
st->fd = -1;
|
||||||
|
|
||||||
free(st);
|
st->xcode = transcode_setup(mfi, &st->size);
|
||||||
db_dispose_item(mfi);
|
if (!st->xcode)
|
||||||
return;
|
{
|
||||||
|
DPRINTF(E_WARN, L_HTTPD, "Transcoding setup failed, aborting streaming\n");
|
||||||
|
|
||||||
|
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
|
||||||
|
|
||||||
|
free(st);
|
||||||
|
db_dispose_item(mfi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!evhttp_find_header(req->output_headers, "Content-Type"))
|
||||||
|
evhttp_add_header(req->output_headers, "Content-Type", "audio/wav");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
ret = stat(mfi->path, &sb);
|
|
||||||
if (ret < 0)
|
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", mfi->path, strerror(errno));
|
/* Stream the raw file */
|
||||||
|
DPRINTF(E_INF, L_HTTPD, "Preparing to stream %s\n", mfi->path);
|
||||||
|
|
||||||
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
stream_cb = stream_chunk_raw_cb;
|
||||||
|
|
||||||
close(st->fd);
|
st->fd = open(mfi->path, O_RDONLY);
|
||||||
free(st);
|
if (st->fd < 0)
|
||||||
db_dispose_item(mfi);
|
{
|
||||||
return;
|
DPRINTF(E_LOG, L_HTTPD, "Could not open %s: %s\n", mfi->path, strerror(errno));
|
||||||
}
|
|
||||||
|
|
||||||
ret = lseek(st->fd, offset, SEEK_SET);
|
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||||
if (ret < 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not seek into %s: %s\n", mfi->path, strerror(errno));
|
|
||||||
|
|
||||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
free(st);
|
||||||
|
db_dispose_item(mfi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
close(st->fd);
|
ret = stat(mfi->path, &sb);
|
||||||
free(st);
|
if (ret < 0)
|
||||||
db_dispose_item(mfi);
|
{
|
||||||
return;
|
DPRINTF(E_LOG, L_HTTPD, "Could not stat() %s: %s\n", mfi->path, strerror(errno));
|
||||||
}
|
|
||||||
|
|
||||||
if (!evhttp_find_header(req->output_headers, "Content-Type") && mfi->type)
|
evhttp_send_error(req, HTTP_NOTFOUND, "Not Found");
|
||||||
{
|
|
||||||
ret = snprintf(buf, sizeof(buf), "audio/%s", mfi->type);
|
close(st->fd);
|
||||||
if ((ret < 0) || (ret >= sizeof(buf)))
|
free(st);
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
|
db_dispose_item(mfi);
|
||||||
else
|
return;
|
||||||
evhttp_add_header(req->output_headers, "Content-Type", buf);
|
}
|
||||||
|
st->size = sb.st_size;
|
||||||
|
|
||||||
|
ret = lseek(st->fd, offset, SEEK_SET);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Could not seek into %s: %s\n", mfi->path, strerror(errno));
|
||||||
|
|
||||||
|
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||||
|
|
||||||
|
close(st->fd);
|
||||||
|
free(st);
|
||||||
|
db_dispose_item(mfi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
st->offset = offset;
|
||||||
|
|
||||||
|
if (!evhttp_find_header(req->output_headers, "Content-Type") && mfi->type)
|
||||||
|
{
|
||||||
|
ret = snprintf(buf, sizeof(buf), "audio/%s", mfi->type);
|
||||||
|
if ((ret < 0) || (ret >= sizeof(buf)))
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Content-Type too large for buffer, dropping\n");
|
||||||
|
else
|
||||||
|
evhttp_add_header(req->output_headers, "Content-Type", buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
st->evbuf = evbuffer_new();
|
st->evbuf = evbuffer_new();
|
||||||
@ -285,14 +379,35 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
evhttp_clear_headers(req->output_headers);
|
evhttp_clear_headers(req->output_headers);
|
||||||
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
|
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
|
||||||
|
|
||||||
close(st->fd);
|
if (transcode)
|
||||||
|
transcode_cleanup(st->xcode);
|
||||||
|
else
|
||||||
|
close(st->fd);
|
||||||
|
free(st);
|
||||||
|
db_dispose_item(mfi);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = evbuffer_expand(st->evbuf, STREAM_CHUNK_SIZE);
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_HTTPD, "Could not expand evbuffer for streaming\n");
|
||||||
|
|
||||||
|
evhttp_clear_headers(req->output_headers);
|
||||||
|
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
|
||||||
|
|
||||||
|
if (transcode)
|
||||||
|
transcode_cleanup(st->xcode);
|
||||||
|
else
|
||||||
|
close(st->fd);
|
||||||
|
evbuffer_free(st->evbuf);
|
||||||
free(st);
|
free(st);
|
||||||
db_dispose_item(mfi);
|
db_dispose_item(mfi);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
evutil_timerclear(&tv);
|
evutil_timerclear(&tv);
|
||||||
ret = event_base_once(evbase_httpd, -1, EV_TIMEOUT, stream_chunk_cb, st, &tv);
|
ret = event_base_once(evbase_httpd, -1, EV_TIMEOUT, stream_cb, st, &tv);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_HTTPD, "Could not add one-shot event for streaming\n");
|
DPRINTF(E_LOG, L_HTTPD, "Could not add one-shot event for streaming\n");
|
||||||
@ -300,7 +415,10 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
evhttp_clear_headers(req->output_headers);
|
evhttp_clear_headers(req->output_headers);
|
||||||
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
|
evhttp_send_error(req, HTTP_SERVUNAVAIL, "Internal Server Error");
|
||||||
|
|
||||||
close(st->fd);
|
if (transcode)
|
||||||
|
transcode_cleanup(st->xcode);
|
||||||
|
else
|
||||||
|
close(st->fd);
|
||||||
evbuffer_free(st->evbuf);
|
evbuffer_free(st->evbuf);
|
||||||
free(st);
|
free(st);
|
||||||
db_dispose_item(mfi);
|
db_dispose_item(mfi);
|
||||||
@ -308,8 +426,7 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
st->id = mfi->id;
|
st->id = mfi->id;
|
||||||
st->size = sb.st_size;
|
st->start_offset = offset;
|
||||||
st->offset = offset;
|
|
||||||
st->req = req;
|
st->req = req;
|
||||||
|
|
||||||
if (offset == 0)
|
if (offset == 0)
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
#include "conffile.h"
|
#include "conffile.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "httpd.h"
|
#include "httpd.h"
|
||||||
|
#include "transcode.h"
|
||||||
#include "httpd_daap.h"
|
#include "httpd_daap.h"
|
||||||
|
|
||||||
|
|
||||||
@ -1111,7 +1112,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
{
|
{
|
||||||
nsongs++;
|
nsongs++;
|
||||||
|
|
||||||
transcode = 0; /* FIXME: No transcode support here yet */
|
transcode = transcode_needed(req->input_headers, dbmfi->codectype);
|
||||||
|
|
||||||
i = -1;
|
i = -1;
|
||||||
while (1)
|
while (1)
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
#include "conffile.h"
|
#include "conffile.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "httpd.h"
|
#include "httpd.h"
|
||||||
|
#include "transcode.h"
|
||||||
#include "httpd_rsp.h"
|
#include "httpd_rsp.h"
|
||||||
|
|
||||||
|
|
||||||
@ -563,7 +564,7 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
|||||||
/* Items block (all items) */
|
/* Items block (all items) */
|
||||||
while (((ret = db_enum_fetch_row(&db_errmsg, &dbmfi, &qi)) == DB_E_SUCCESS) && (dbmfi))
|
while (((ret = db_enum_fetch_row(&db_errmsg, &dbmfi, &qi)) == DB_E_SUCCESS) && (dbmfi))
|
||||||
{
|
{
|
||||||
transcode = 0; /* FIXME: No transcode support here yet */
|
transcode = transcode_needed(req->input_headers, dbmfi->codectype);
|
||||||
|
|
||||||
/* Item block (one item) */
|
/* Item block (one item) */
|
||||||
item = mxmlNewElement(items, "item");
|
item = mxmlNewElement(items, "item");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user