mirror of https://code.videolan.org/videolan/vlc
931 lines
26 KiB
C
931 lines
26 KiB
C
/*****************************************************************************
|
|
* stream.c
|
|
*****************************************************************************
|
|
* Copyright (C) 1999-2004 VLC authors and VideoLAN
|
|
* Copyright 2008-2015 Rémi Denis-Courmont
|
|
*
|
|
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
|
|
*
|
|
* This program 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 program 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.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdalign.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_block.h>
|
|
#include <vlc_access.h>
|
|
#include <vlc_charset.h>
|
|
#include <vlc_interrupt.h>
|
|
#include <vlc_stream_extractor.h>
|
|
|
|
#include <libvlc.h>
|
|
#include "stream.h"
|
|
#include "mrl_helpers.h"
|
|
|
|
typedef struct stream_priv_t
|
|
{
|
|
stream_t stream;
|
|
void (*destroy)(stream_t *);
|
|
block_t *block;
|
|
block_t *peek;
|
|
uint64_t offset;
|
|
bool eof;
|
|
|
|
/* UTF-16 and UTF-32 file reading */
|
|
struct {
|
|
vlc_iconv_t conv;
|
|
unsigned char char_width;
|
|
bool little_endian;
|
|
} text;
|
|
|
|
max_align_t private_data[];
|
|
} stream_priv_t;
|
|
|
|
/**
|
|
* Allocates a VLC stream object
|
|
*/
|
|
stream_t *vlc_stream_CustomNew(vlc_object_t *parent,
|
|
void (*destroy)(stream_t *), size_t size,
|
|
const char *type_name)
|
|
{
|
|
stream_priv_t *priv = vlc_custom_create(parent, sizeof (*priv) + size,
|
|
type_name);
|
|
if (unlikely(priv == NULL))
|
|
return NULL;
|
|
|
|
stream_t *s = &priv->stream;
|
|
|
|
s->psz_name = NULL;
|
|
s->psz_url = NULL;
|
|
s->psz_location = NULL;
|
|
s->psz_filepath = NULL;
|
|
s->b_preparsing = false;
|
|
s->p_input_item = NULL;
|
|
s->s = NULL;
|
|
s->out = NULL;
|
|
s->pf_read = NULL;
|
|
s->pf_block = NULL;
|
|
s->pf_readdir = NULL;
|
|
s->pf_demux = NULL;
|
|
s->pf_seek = NULL;
|
|
s->pf_control = NULL;
|
|
s->p_sys = NULL;
|
|
s->ops = NULL;
|
|
assert(destroy != NULL);
|
|
priv->destroy = destroy;
|
|
priv->block = NULL;
|
|
priv->peek = NULL;
|
|
priv->offset = 0;
|
|
priv->eof = false;
|
|
|
|
/* UTF16 and UTF32 text file conversion */
|
|
priv->text.conv = (vlc_iconv_t)(-1);
|
|
priv->text.char_width = 1;
|
|
priv->text.little_endian = false;
|
|
|
|
return &priv->stream;
|
|
}
|
|
|
|
#define stream_priv(s) container_of(s, stream_priv_t, stream)
|
|
|
|
void *vlc_stream_Private(stream_t *stream)
|
|
{
|
|
return stream_priv(stream)->private_data;
|
|
}
|
|
|
|
stream_t *vlc_stream_CommonNew(vlc_object_t *parent,
|
|
void (*destroy)(stream_t *))
|
|
{
|
|
return vlc_stream_CustomNew(parent, destroy, 0, "stream");
|
|
}
|
|
|
|
void stream_CommonDelete(stream_t *s)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
|
|
if (priv->text.conv != (vlc_iconv_t)(-1))
|
|
vlc_iconv_close(priv->text.conv);
|
|
|
|
if (priv->peek != NULL)
|
|
block_Release(priv->peek);
|
|
if (priv->block != NULL)
|
|
block_Release(priv->block);
|
|
|
|
free(s->psz_url);
|
|
vlc_object_delete(s);
|
|
}
|
|
|
|
/**
|
|
* Destroy a stream
|
|
*/
|
|
void vlc_stream_Delete(stream_t *s)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
|
|
if (s->ops != NULL && s->ops->close != NULL) {
|
|
s->ops->close(s);
|
|
}
|
|
priv->destroy(s);
|
|
stream_CommonDelete(s);
|
|
}
|
|
|
|
stream_t *(vlc_stream_NewURL)(vlc_object_t *p_parent, const char *psz_url)
|
|
{
|
|
if( !psz_url )
|
|
return NULL;
|
|
|
|
stream_t *s = stream_AccessNew( p_parent, NULL, NULL, false, psz_url );
|
|
if( s == NULL )
|
|
msg_Err( p_parent, "no suitable access module for `%s'", psz_url );
|
|
else
|
|
s = stream_FilterAutoNew(s);
|
|
return s;
|
|
}
|
|
|
|
stream_t *(vlc_stream_NewMRL)(vlc_object_t* parent, const char* mrl )
|
|
{
|
|
stream_t* stream = vlc_stream_NewURL( parent, mrl );
|
|
|
|
if( stream == NULL )
|
|
return NULL;
|
|
|
|
char const* anchor = strchr( mrl, '#' );
|
|
|
|
if( anchor == NULL )
|
|
return stream;
|
|
|
|
char const* extra;
|
|
if( stream_extractor_AttachParsed( &stream, anchor + 1, &extra ) )
|
|
{
|
|
msg_Err( parent, "unable to open %s", mrl );
|
|
vlc_stream_Delete( stream );
|
|
return NULL;
|
|
}
|
|
|
|
if( extra && *extra )
|
|
msg_Warn( parent, "ignoring extra fragment data: %s", extra );
|
|
|
|
return stream;
|
|
}
|
|
|
|
/**
|
|
* Read from the stream until first newline.
|
|
* \param s Stream handle to read from
|
|
* \return A pointer to the allocated output string. You need to free this when you are done.
|
|
*/
|
|
#define STREAM_PROBE_LINE 2048
|
|
#define STREAM_LINE_MAX (2048*100)
|
|
char *vlc_stream_ReadLine( stream_t *s )
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
|
|
/* Let's fail quickly if this is a readdir access */
|
|
if( (s->ops != NULL && s->ops->stream.read == NULL && s->ops->stream.block == NULL) ||
|
|
(s->ops == NULL && s->pf_read == NULL && s->pf_block == NULL) )
|
|
return NULL;
|
|
|
|
/* BOM detection */
|
|
if( vlc_stream_Tell( s ) == 0 )
|
|
{
|
|
const uint8_t *p_data;
|
|
ssize_t i_data = vlc_stream_Peek( s, &p_data, 2 );
|
|
|
|
if( i_data <= 0 )
|
|
return NULL;
|
|
|
|
if( unlikely(priv->text.conv != (vlc_iconv_t)-1) )
|
|
{ /* seek back to beginning? reset */
|
|
vlc_iconv_close( priv->text.conv );
|
|
priv->text.conv = (vlc_iconv_t)-1;
|
|
}
|
|
priv->text.char_width = 1;
|
|
priv->text.little_endian = false;
|
|
|
|
if( i_data >= 2 )
|
|
{
|
|
const char *psz_encoding = NULL;
|
|
bool little_endian = false;
|
|
|
|
if( !memcmp( p_data, "\xFF\xFE", 2 ) )
|
|
{
|
|
psz_encoding = "UTF-16LE";
|
|
little_endian = true;
|
|
}
|
|
else if( !memcmp( p_data, "\xFE\xFF", 2 ) )
|
|
{
|
|
psz_encoding = "UTF-16BE";
|
|
}
|
|
|
|
/* Open the converter if we need it */
|
|
if( psz_encoding != NULL )
|
|
{
|
|
msg_Dbg( s, "UTF-16 BOM detected" );
|
|
priv->text.conv = vlc_iconv_open( "UTF-8", psz_encoding );
|
|
if( unlikely(priv->text.conv == (vlc_iconv_t)-1) )
|
|
{
|
|
msg_Err( s, "iconv_open failed" );
|
|
return NULL;
|
|
}
|
|
priv->text.char_width = 2;
|
|
priv->text.little_endian = little_endian;
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t i_line = 0;
|
|
const uint8_t *p_data;
|
|
|
|
for( ;; )
|
|
{
|
|
size_t i_peek = i_line == 0 ? STREAM_PROBE_LINE
|
|
: __MIN( i_line * 2, STREAM_LINE_MAX );
|
|
|
|
/* Probe more data */
|
|
ssize_t i_data = vlc_stream_Peek( s, &p_data, i_peek );
|
|
if( i_data <= 0 )
|
|
return NULL;
|
|
|
|
/* Deal here with lone-byte incomplete UTF-16 sequences at EOF
|
|
that we won't be able to process anyway */
|
|
if( i_data < priv->text.char_width )
|
|
{
|
|
assert( priv->text.char_width == 2 );
|
|
uint8_t inc;
|
|
ssize_t i_inc = vlc_stream_Read( s, &inc, priv->text.char_width );
|
|
assert( i_inc == i_data );
|
|
if( i_inc > 0 )
|
|
msg_Err( s, "discarding incomplete UTF-16 sequence at EOF: 0x%02x", inc );
|
|
return NULL;
|
|
}
|
|
|
|
/* Keep to text encoding character width boundary */
|
|
if( i_data % priv->text.char_width )
|
|
i_data = i_data - ( i_data % priv->text.char_width );
|
|
|
|
if( (size_t) i_data == i_line )
|
|
break; /* No more data */
|
|
|
|
assert( (size_t) i_data > i_line );
|
|
|
|
/* Resume search for an EOL where we left off */
|
|
const uint8_t *p_cur = p_data + i_line, *psz_eol;
|
|
|
|
/* FIXME: <CR> behavior varies depending on where buffer
|
|
boundaries happen to fall; a <CR><LF> across the boundary
|
|
creates a bogus empty line. */
|
|
if( priv->text.char_width == 1 )
|
|
{
|
|
/* UTF-8: 0A <LF> */
|
|
psz_eol = memchr( p_cur, '\n', i_data - i_line );
|
|
if( psz_eol == NULL )
|
|
/* UTF-8: 0D <CR> */
|
|
psz_eol = memchr( p_cur, '\r', i_data - i_line );
|
|
}
|
|
else
|
|
{
|
|
const uint8_t *p_last = p_data + i_data - priv->text.char_width;
|
|
uint16_t eol = priv->text.little_endian ? 0x0A00 : 0x000A;
|
|
|
|
assert( priv->text.char_width == 2 );
|
|
psz_eol = NULL;
|
|
/* UTF-16: 000A <LF> */
|
|
for( const uint8_t *p = p_cur; p <= p_last; p += 2 )
|
|
{
|
|
if( U16_AT( p ) == eol )
|
|
{
|
|
psz_eol = p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( psz_eol == NULL )
|
|
{ /* UTF-16: 000D <CR> */
|
|
eol = priv->text.little_endian ? 0x0D00 : 0x000D;
|
|
for( const uint8_t *p = p_cur; p <= p_last; p += 2 )
|
|
{
|
|
if( U16_AT( p ) == eol )
|
|
{
|
|
psz_eol = p;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( psz_eol )
|
|
{
|
|
i_line = (psz_eol - p_data) + priv->text.char_width;
|
|
/* We have our line */
|
|
break;
|
|
}
|
|
|
|
i_line = i_data;
|
|
|
|
if( i_line >= STREAM_LINE_MAX )
|
|
{
|
|
msg_Err( s, "line too long, exceeding %zu bytes",
|
|
(size_t) STREAM_LINE_MAX );
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if( i_line == 0 ) /* We failed to read any data, probably EOF */
|
|
return NULL;
|
|
|
|
/* If encoding conversion is required, UTF-8 needs at most 150%
|
|
as long a buffer as UTF-16 */
|
|
size_t i_line_conv = priv->text.char_width == 1 ? i_line : i_line * 3 / 2;
|
|
char *p_line = malloc( i_line_conv + 1 ); /* +1 for easy \0 append */
|
|
if( !p_line )
|
|
return NULL;
|
|
void *p_read = p_line;
|
|
|
|
if( priv->text.char_width > 1 )
|
|
{
|
|
size_t i_in = i_line, i_out = i_line_conv;
|
|
const char * p_in = (char *) p_data;
|
|
char * p_out = p_line;
|
|
|
|
if( vlc_iconv( priv->text.conv, &p_in, &i_in, &p_out, &i_out ) == VLC_ICONV_ERR )
|
|
{
|
|
msg_Err( s, "conversion error: %s", vlc_strerror_c( errno ) );
|
|
msg_Dbg( s, "original: %zu, in %zu, out %zu", i_line, i_in, i_out );
|
|
/* Reset state */
|
|
size_t r = vlc_iconv( priv->text.conv, NULL, NULL, NULL, NULL );
|
|
VLC_UNUSED( r );
|
|
/* FIXME: the rest of the line is discarded and lost */
|
|
}
|
|
|
|
i_line_conv -= i_out;
|
|
p_read = NULL; /* Line already read, only need to advance the stream */
|
|
}
|
|
|
|
ssize_t i_data = vlc_stream_Read( s, p_read, i_line );
|
|
assert( i_data > 0 && (size_t) i_data == i_line );
|
|
if( i_data <= 0 )
|
|
{
|
|
/* Hmmm */
|
|
free( p_line );
|
|
return NULL;
|
|
}
|
|
|
|
i_line = i_line_conv;
|
|
|
|
/* Remove trailing LF/CR */
|
|
while( i_line >= 1 &&
|
|
(p_line[i_line - 1] == '\r' || p_line[i_line - 1] == '\n') )
|
|
i_line--;
|
|
|
|
/* Make sure the \0 is there */
|
|
p_line[i_line] = '\0';
|
|
|
|
return p_line;
|
|
}
|
|
|
|
static ssize_t vlc_stream_CopyBlock(block_t **restrict pp,
|
|
void *buf, size_t len)
|
|
{
|
|
block_t *block = *pp;
|
|
|
|
if (block == NULL)
|
|
return -1;
|
|
|
|
if (len > block->i_buffer)
|
|
len = block->i_buffer;
|
|
|
|
if (buf != NULL)
|
|
memcpy(buf, block->p_buffer, len);
|
|
|
|
block->p_buffer += len;
|
|
block->i_buffer -= len;
|
|
|
|
if (block->i_buffer == 0)
|
|
{
|
|
block_Release(block);
|
|
*pp = NULL;
|
|
}
|
|
|
|
return likely(len > 0) ? (ssize_t)len : -1;
|
|
}
|
|
|
|
static ssize_t vlc_stream_ReadRaw(stream_t *s, void *buf, size_t len)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
ssize_t ret;
|
|
|
|
assert(len <= SSIZE_MAX);
|
|
|
|
if (vlc_killed())
|
|
return 0;
|
|
|
|
if ((s->ops != NULL && s->ops->stream.read != NULL) || (s->ops == NULL && s->pf_read != NULL))
|
|
{
|
|
assert(priv->block == NULL);
|
|
if (buf == NULL)
|
|
{
|
|
if (unlikely(len == 0))
|
|
return 0;
|
|
|
|
char dummy[256];
|
|
ret = (s->ops != NULL ? s->ops->stream.read : s->pf_read)(s, dummy, len <= 256 ? len : 256);
|
|
}
|
|
else
|
|
ret = (s->ops != NULL ? s->ops->stream.read : s->pf_read)(s, buf, len);
|
|
return ret;
|
|
}
|
|
|
|
ret = vlc_stream_CopyBlock(&priv->block, buf, len);
|
|
if (ret >= 0)
|
|
return ret;
|
|
|
|
if ((s->ops != NULL && s->ops->stream.block != NULL) || (s->ops == NULL && s->pf_block != NULL))
|
|
{
|
|
bool eof = false;
|
|
|
|
priv->block = (s->ops != NULL ? s->ops->stream.block : s->pf_block)(s, &eof);
|
|
ret = vlc_stream_CopyBlock(&priv->block, buf, len);
|
|
if (ret >= 0)
|
|
return ret;
|
|
return eof ? 0 : -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t vlc_stream_ReadPartial(stream_t *s, void *buf, size_t len)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
ssize_t ret;
|
|
|
|
ret = vlc_stream_CopyBlock(&priv->peek, buf, len);
|
|
if (ret >= 0)
|
|
{
|
|
priv->offset += ret;
|
|
assert(ret <= (ssize_t)len);
|
|
return ret;
|
|
}
|
|
|
|
ret = vlc_stream_ReadRaw(s, buf, len);
|
|
if (ret > 0)
|
|
priv->offset += ret;
|
|
if (ret == 0)
|
|
priv->eof = len != 0;
|
|
assert(ret <= (ssize_t)len);
|
|
return ret;
|
|
}
|
|
|
|
ssize_t vlc_stream_Read(stream_t *s, void *buf, size_t len)
|
|
{
|
|
size_t copied = 0;
|
|
|
|
while (len > 0)
|
|
{
|
|
ssize_t ret = vlc_stream_ReadPartial(s, buf, len);
|
|
if (ret < 0)
|
|
continue;
|
|
if (ret == 0)
|
|
break;
|
|
|
|
if (buf != NULL)
|
|
buf = (char *)buf + ret;
|
|
assert(len >= (size_t)ret);
|
|
len -= ret;
|
|
copied += ret;
|
|
}
|
|
|
|
return copied;
|
|
}
|
|
|
|
ssize_t vlc_stream_Peek(stream_t *s, const uint8_t **restrict bufp, size_t len)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
block_t *peek;
|
|
|
|
peek = priv->peek;
|
|
if (peek == NULL)
|
|
{
|
|
peek = priv->block;
|
|
priv->peek = peek;
|
|
priv->block = NULL;
|
|
}
|
|
|
|
if (peek == NULL)
|
|
{
|
|
peek = block_Alloc(len);
|
|
if (unlikely(peek == NULL))
|
|
return VLC_ENOMEM;
|
|
|
|
peek->i_buffer = 0;
|
|
}
|
|
else
|
|
if (peek->i_buffer < len)
|
|
{
|
|
size_t avail = peek->i_buffer;
|
|
|
|
peek = block_TryRealloc(peek, 0, len);
|
|
if (unlikely(peek == NULL))
|
|
return VLC_ENOMEM;
|
|
|
|
peek->i_buffer = avail;
|
|
}
|
|
|
|
priv->peek = peek;
|
|
*bufp = peek->p_buffer;
|
|
|
|
while (peek->i_buffer < len)
|
|
{
|
|
size_t avail = peek->i_buffer;
|
|
ssize_t ret;
|
|
|
|
ret = vlc_stream_ReadRaw(s, peek->p_buffer + avail, len - avail);
|
|
if (ret < 0)
|
|
continue;
|
|
|
|
peek->i_buffer += ret;
|
|
|
|
if (ret == 0)
|
|
return peek->i_buffer;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
block_t *vlc_stream_ReadBlock(stream_t *s)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
block_t *block;
|
|
|
|
if (vlc_killed())
|
|
{
|
|
priv->eof = true;
|
|
return NULL;
|
|
}
|
|
|
|
if (priv->peek != NULL)
|
|
{
|
|
block = priv->peek;
|
|
priv->peek = NULL;
|
|
}
|
|
else if (priv->block != NULL)
|
|
{
|
|
block = priv->block;
|
|
priv->block = NULL;
|
|
}
|
|
else if ((s->ops != NULL && s->ops->stream.block != NULL) || (s->ops == NULL && s->pf_block != NULL))
|
|
{
|
|
priv->eof = false;
|
|
block = (s->ops != NULL ? s->ops->stream.block : s->pf_block)(s, &priv->eof);
|
|
}
|
|
else
|
|
{
|
|
block = block_Alloc(4096);
|
|
if (unlikely(block == NULL))
|
|
return NULL;
|
|
|
|
ssize_t ret = (s->ops != NULL ? s->ops->stream.read : s->pf_read)(s, block->p_buffer, block->i_buffer);
|
|
if (ret > 0)
|
|
block->i_buffer = ret;
|
|
else
|
|
{
|
|
block_Release(block);
|
|
block = NULL;
|
|
}
|
|
|
|
priv->eof = !ret;
|
|
}
|
|
|
|
if (block != NULL)
|
|
priv->offset += block->i_buffer;
|
|
|
|
return block;
|
|
}
|
|
|
|
uint64_t vlc_stream_Tell(const stream_t *s)
|
|
{
|
|
const stream_priv_t *priv = stream_priv(s);
|
|
|
|
return priv->offset;
|
|
}
|
|
|
|
bool vlc_stream_Eof(const stream_t *s)
|
|
{
|
|
const stream_priv_t *priv = stream_priv(s);
|
|
|
|
return priv->eof;
|
|
}
|
|
|
|
int vlc_stream_Seek(stream_t *s, uint64_t offset)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
|
|
priv->eof = false;
|
|
|
|
block_t *peek = priv->peek;
|
|
if (peek != NULL)
|
|
{
|
|
if (offset >= priv->offset
|
|
&& offset <= (priv->offset + peek->i_buffer))
|
|
{ /* Seeking within the peek buffer */
|
|
size_t fwd = offset - priv->offset;
|
|
|
|
peek->p_buffer += fwd;
|
|
peek->i_buffer -= fwd;
|
|
priv->offset = offset;
|
|
|
|
if (peek->i_buffer == 0)
|
|
{
|
|
priv->peek = NULL;
|
|
block_Release(peek);
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (priv->offset == offset)
|
|
return VLC_SUCCESS; /* Nothing to do! */
|
|
}
|
|
|
|
int ret;
|
|
if (s->ops == NULL && s->pf_seek != NULL) {
|
|
ret = s->pf_seek(s, offset);
|
|
} else if (s->ops != NULL && s->ops->stream.seek != NULL) {
|
|
ret = s->ops->stream.seek(s, offset);
|
|
} else {
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
if (ret != VLC_SUCCESS)
|
|
return ret;
|
|
|
|
priv->offset = offset;
|
|
|
|
if (peek != NULL)
|
|
{
|
|
priv->peek = NULL;
|
|
block_Release(peek);
|
|
}
|
|
|
|
if (priv->block != NULL)
|
|
{
|
|
block_Release(priv->block);
|
|
priv->block = NULL;
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Use to control the "stream_t *". Look at #stream_query_e for
|
|
* possible "i_query" value and format arguments. Return VLC_SUCCESS
|
|
* if ... succeed ;) and VLC_EGENERIC if failed or unimplemented
|
|
*/
|
|
int vlc_stream_vaControl(stream_t *s, int cmd, va_list args)
|
|
{
|
|
stream_priv_t *priv = stream_priv(s);
|
|
|
|
switch (cmd)
|
|
{
|
|
case STREAM_SET_TITLE:
|
|
case STREAM_SET_SEEKPOINT:
|
|
{
|
|
int ret;
|
|
|
|
if (s->ops != NULL && cmd == STREAM_SET_TITLE) {
|
|
int title = va_arg(args, int);
|
|
ret = s->ops->set_title(s, title);
|
|
} else if (s->ops != NULL && cmd == STREAM_SET_SEEKPOINT) {
|
|
int seekpoint = va_arg(args, int);
|
|
ret = s->ops->set_seek_point(s, seekpoint);
|
|
} else {
|
|
ret = s->pf_control(s, cmd, args);
|
|
}
|
|
|
|
if (ret != VLC_SUCCESS)
|
|
return ret;
|
|
|
|
priv->offset = 0;
|
|
|
|
if (priv->peek != NULL)
|
|
{
|
|
block_Release(priv->peek);
|
|
priv->peek = NULL;
|
|
}
|
|
|
|
if (priv->block != NULL)
|
|
{
|
|
block_Release(priv->block);
|
|
priv->block = NULL;
|
|
}
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if (s->ops == NULL)
|
|
return s->pf_control(s, cmd, args);
|
|
|
|
switch (cmd) {
|
|
case STREAM_CAN_SEEK:
|
|
{
|
|
bool *can_seek = va_arg(args, bool *);
|
|
if (s->ops->can_seek != NULL) {
|
|
*can_seek = s->ops->can_seek(s);
|
|
} else {
|
|
*can_seek = false;
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
case STREAM_CAN_FASTSEEK:
|
|
{
|
|
bool *can_fastseek = va_arg(args, bool *);
|
|
if (s->ops->stream.can_fastseek != NULL) {
|
|
*can_fastseek = s->ops->stream.can_fastseek(s);
|
|
} else {
|
|
*can_fastseek = false;
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
case STREAM_CAN_PAUSE:
|
|
{
|
|
bool *can_pause = va_arg(args, bool *);
|
|
if (s->ops->can_pause != NULL) {
|
|
*can_pause = s->ops->can_pause(s);
|
|
} else {
|
|
*can_pause = false;
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
case STREAM_CAN_CONTROL_PACE:
|
|
{
|
|
bool *can_control_pace = va_arg(args, bool *);
|
|
if (s->ops->can_control_pace != NULL) {
|
|
*can_control_pace = s->ops->can_control_pace(s);
|
|
} else {
|
|
*can_control_pace = false;
|
|
}
|
|
return VLC_SUCCESS;
|
|
}
|
|
case STREAM_GET_SIZE:
|
|
if (s->ops->stream.get_size != NULL) {
|
|
uint64_t *size = va_arg(args, uint64_t *);
|
|
return s->ops->stream.get_size(s, size);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_PTS_DELAY:
|
|
if (s->ops->get_pts_delay != NULL) {
|
|
vlc_tick_t *pts_delay = va_arg(args, vlc_tick_t *);
|
|
return s->ops->get_pts_delay(s, pts_delay);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_TITLE_INFO:
|
|
if (s->ops->stream.get_title_info != NULL) {
|
|
input_title_t ***title_info = va_arg(args, input_title_t ***);
|
|
int *unk = va_arg(args, int *);
|
|
return s->ops->stream.get_title_info(s, title_info, unk);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_TITLE:
|
|
if (s->ops->stream.get_title != NULL) {
|
|
unsigned *title = va_arg(args, unsigned *);
|
|
return s->ops->stream.get_title(s, title);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_SEEKPOINT:
|
|
if (s->ops->stream.get_seekpoint != NULL) {
|
|
unsigned *seekpoint = va_arg(args, unsigned *);
|
|
return s->ops->stream.get_seekpoint(s, seekpoint);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_META:
|
|
if (s->ops->get_meta != NULL) {
|
|
vlc_meta_t *meta = va_arg(args, vlc_meta_t *);
|
|
return s->ops->get_meta(s, meta);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_CONTENT_TYPE:
|
|
if (s->ops->stream.get_content_type != NULL) {
|
|
char **content_type = va_arg(args, char **);
|
|
return s->ops->stream.get_content_type(s, content_type);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_SIGNAL:
|
|
if (s->ops->get_signal != NULL) {
|
|
double *quality = va_arg(args, double *);
|
|
double *strength = va_arg(args, double *);
|
|
return s->ops->get_signal(s, quality, strength);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_TAGS:
|
|
if (s->ops->stream.get_tags != NULL) {
|
|
const block_t **block = va_arg(args, const block_t **);
|
|
return s->ops->stream.get_tags(s, block);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_TYPE:
|
|
if (s->ops->get_type != NULL) {
|
|
int *type = va_arg(args, int *);
|
|
return s->ops->get_type(s, type);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_GET_PRIVATE_ID_STATE:
|
|
if (s->ops->stream.get_private_id_state != NULL) {
|
|
int priv_data = va_arg(args, int);
|
|
bool *selected = va_arg(args, bool *);
|
|
return s->ops->stream.get_private_id_state(s, priv_data, selected);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_SET_PAUSE_STATE:
|
|
if (s->ops->set_pause_state != NULL) {
|
|
bool pause_state = (bool)va_arg(args, int);
|
|
return s->ops->set_pause_state(s, pause_state);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_SET_RECORD_STATE:
|
|
if (s->ops->stream.set_record_state != NULL) {
|
|
bool record_state = (bool)va_arg(args, int);
|
|
const char *dir_path = NULL;
|
|
const char *ext = NULL;
|
|
if (record_state) {
|
|
dir_path = va_arg(args, const char *);
|
|
ext = va_arg(args, const char *);
|
|
}
|
|
return s->ops->stream.set_record_state(s, record_state, dir_path, ext);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_SET_PRIVATE_ID_STATE:
|
|
if (s->ops->stream.set_private_id_state != NULL) {
|
|
int priv_data = va_arg(args, int);
|
|
bool selected = (bool)va_arg(args, int);
|
|
return s->ops->stream.set_private_id_state(s, priv_data, selected);
|
|
}
|
|
return VLC_EGENERIC;
|
|
case STREAM_SET_PRIVATE_ID_CA:
|
|
if (s->ops->stream.set_private_id_ca != NULL) {
|
|
void *payload = va_arg(args, void *);
|
|
return s->ops->stream.set_private_id_ca(s, payload);
|
|
}
|
|
return VLC_EGENERIC;
|
|
default:
|
|
vlc_assert_unreachable();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read data into a block.
|
|
*
|
|
* @param s stream to read data from
|
|
* @param size number of bytes to read
|
|
* @return a block of data, or NULL on error
|
|
@ note The block size may be shorter than requested if the end-of-stream was
|
|
* reached.
|
|
*/
|
|
block_t *vlc_stream_Block( stream_t *s, size_t size )
|
|
{
|
|
if( unlikely(size > SSIZE_MAX) )
|
|
return NULL;
|
|
|
|
block_t *block = block_Alloc( size );
|
|
if( unlikely(block == NULL) )
|
|
return NULL;
|
|
|
|
ssize_t val = vlc_stream_Read( s, block->p_buffer, size );
|
|
if( val <= 0 )
|
|
{
|
|
block_Release( block );
|
|
return NULL;
|
|
}
|
|
|
|
block->i_buffer = val;
|
|
return block;
|
|
}
|
|
|
|
int vlc_stream_ReadDir( stream_t *s, input_item_node_t *p_node )
|
|
{
|
|
assert(s->pf_readdir != NULL || (s->ops != NULL && s->ops->stream.readdir != NULL));
|
|
return (s->ops != NULL ? s->ops->stream.readdir : s->pf_readdir)( s, p_node );
|
|
}
|