2010-08-19 14:54:37 +00:00
/*
* Apple HTTP Live Streaming demuxer
* Copyright ( c ) 2010 Martin Storsjo
2013-12-28 00:09:45 +02:00
* Copyright ( c ) 2013 Anssi Hannula
2021-09-22 00:12:00 +05:30
* Copyright ( c ) 2021 Nachiket Tarate
2010-08-19 14:54:37 +00:00
*
* This file is part of FFmpeg .
*
* FFmpeg 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 .
*
* FFmpeg 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 FFmpeg ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
/**
* @ file
* Apple HTTP Live Streaming demuxer
2020-12-23 17:32:34 +08:00
* https : //www.rfc-editor.org/rfc/rfc8216.txt
2010-08-19 14:54:37 +00:00
*/
2022-02-23 14:56:49 +02:00
# include "config_components.h"
2017-10-04 14:52:52 -07:00
# include "libavformat/http.h"
2022-03-22 13:06:05 +01:00
# include "libavutil/aes.h"
2010-08-19 14:54:37 +00:00
# include "libavutil/avstring.h"
2014-04-05 14:47:26 +03:00
# include "libavutil/avassert.h"
2011-01-23 23:42:18 +02:00
# include "libavutil/intreadwrite.h"
2011-06-04 12:58:23 +01:00
# include "libavutil/mathematics.h"
2024-03-25 01:30:37 +01:00
# include "libavutil/mem.h"
2011-01-23 23:42:18 +02:00
# include "libavutil/opt.h"
2011-05-22 12:46:29 +02:00
# include "libavutil/dict.h"
2012-06-21 20:31:44 +01:00
# include "libavutil/time.h"
2010-08-19 14:54:37 +00:00
# include "avformat.h"
2022-05-06 18:28:08 +02:00
# include "demux.h"
2010-08-19 14:54:37 +00:00
# include "internal.h"
2011-03-21 00:21:56 +02:00
# include "avio_internal.h"
2014-04-05 14:47:26 +03:00
# include "id3v2.h"
2023-09-06 20:21:59 +02:00
# include "url.h"
2011-03-21 00:21:56 +02:00
2021-09-22 00:12:00 +05:30
# include "hls_sample_encryption.h"
2011-03-21 00:21:56 +02:00
# define INITIAL_BUFFER_SIZE 32768
2010-08-19 14:54:37 +00:00
2013-12-28 00:09:45 +02:00
# define MAX_FIELD_LEN 64
# define MAX_CHARACTERISTICS_LEN 512
2014-04-05 14:47:26 +03:00
# define MPEG_TIME_BASE 90000
# define MPEG_TIME_BASE_Q (AVRational){1, MPEG_TIME_BASE}
2010-08-19 14:54:37 +00:00
/*
* An apple http stream consists of a playlist with media segment files ,
* played sequentially . There may be several playlists with the same
* video content , in different bandwidth variants , that are played in
2012-07-05 15:38:53 -08:00
* parallel ( preferably only one bandwidth variant at a time ) . In this case ,
2010-08-19 14:54:37 +00:00
* the user supplied the url to a main playlist that only lists the variant
* playlists .
*
* If the main playlist doesn ' t point at any variants , we still create
* one anonymous toplevel variant for this , to maintain the structure .
*/
2011-01-23 23:42:18 +02:00
enum KeyType {
KEY_NONE ,
KEY_AES_128 ,
2015-03-05 19:49:57 +01:00
KEY_SAMPLE_AES
2011-01-23 23:42:18 +02:00
} ;
2010-08-19 14:54:37 +00:00
struct segment {
2013-07-29 10:26:02 +03:00
int64_t duration ;
2013-12-28 09:42:46 +02:00
int64_t url_offset ;
int64_t size ;
2014-04-11 08:14:40 +02:00
char * url ;
char * key ;
2011-01-23 23:42:18 +02:00
enum KeyType key_type ;
uint8_t iv [ 16 ] ;
2015-10-15 14:23:00 +03:00
/* associated Media Initialization Section, treated as a segment */
struct segment * init_section ;
2010-08-19 14:54:37 +00:00
} ;
2013-12-28 00:09:45 +02:00
struct rendition ;
2014-01-05 02:18:12 +04:00
enum PlaylistType {
PLS_TYPE_UNSPECIFIED ,
PLS_TYPE_EVENT ,
PLS_TYPE_VOD
} ;
2010-08-19 14:54:37 +00:00
/*
2013-12-27 13:01:10 +02:00
* Each playlist has its own demuxer . If it currently is active ,
2011-02-20 11:04:12 +01:00
* it has an open AVIOContext too , and potentially an AVPacket
2010-08-19 14:54:37 +00:00
* containing the next packet from this stream .
*/
2013-12-27 13:01:10 +02:00
struct playlist {
2010-08-19 14:54:37 +00:00
char url [ MAX_URL_SIZE ] ;
2021-08-04 16:52:07 +02:00
FFIOContext pb ;
2011-03-21 00:21:56 +02:00
uint8_t * read_buffer ;
2016-01-20 11:43:18 +01:00
AVIOContext * input ;
2017-10-04 14:52:52 -07:00
int input_read_done ;
2017-12-12 15:25:46 -08:00
AVIOContext * input_next ;
int input_next_requested ;
2011-03-21 00:21:56 +02:00
AVFormatContext * parent ;
int index ;
2010-08-19 14:54:37 +00:00
AVFormatContext * ctx ;
2021-01-29 10:27:05 -03:00
AVPacket * pkt ;
2016-07-26 11:33:38 +03:00
int has_noheader_flag ;
2016-07-27 23:29:16 +03:00
/* main demuxer streams associated with this playlist
* indexed by the subdemuxer stream indexes */
AVStream * * main_streams ;
int n_main_streams ;
2010-08-19 14:54:37 +00:00
2011-03-21 00:08:59 +02:00
int finished ;
2014-01-05 02:18:12 +04:00
enum PlaylistType type ;
2013-07-29 10:26:02 +03:00
int64_t target_duration ;
2021-01-16 11:40:36 +08:00
int64_t start_seq_no ;
2022-06-23 00:55:38 +08:00
int time_offset_flag ;
int64_t start_time_offset ;
2010-08-19 14:54:37 +00:00
int n_segments ;
struct segment * * segments ;
2017-11-19 19:30:02 +02:00
int needed ;
2019-09-03 09:55:17 +08:00
int broken ;
2021-01-16 11:40:36 +08:00
int64_t cur_seq_no ;
int64_t last_seq_no ;
2019-11-18 17:37:00 +08:00
int m3u8_hold_counters ;
2013-12-28 09:42:46 +02:00
int64_t cur_seg_offset ;
2011-03-21 00:21:56 +02:00
int64_t last_load_time ;
2011-01-23 23:42:18 +02:00
2015-10-15 14:23:00 +03:00
/* Currently active Media Initialization Section */
struct segment * cur_init_section ;
uint8_t * init_sec_buf ;
unsigned int init_sec_buf_size ;
unsigned int init_sec_data_len ;
unsigned int init_sec_buf_read_offset ;
2011-01-23 23:42:18 +02:00
char key_url [ MAX_URL_SIZE ] ;
uint8_t key [ 16 ] ;
2013-12-28 00:09:45 +02:00
2014-04-05 14:47:26 +03:00
/* ID3 timestamp handling (elementary audio streams have ID3 timestamps
* ( and possibly other ID3 tags ) in the beginning of each segment ) */
int is_id3_timestamped ; /* -1: not yet known */
int64_t id3_mpegts_timestamp ; /* in mpegts tb */
int64_t id3_offset ; /* in stream original tb */
uint8_t * id3_buf ; /* temp buffer for id3 parsing */
unsigned int id3_buf_size ;
AVDictionary * id3_initial ; /* data from first id3 tag */
int id3_found ; /* ID3 tag found at some point */
int id3_changed ; /* ID3 tag data has changed at some point */
ID3v2ExtraMeta * id3_deferred_extra ; /* stored here until subdemuxer is opened */
2021-09-22 00:12:00 +05:30
HLSAudioSetupInfo audio_setup_info ;
2013-12-30 11:13:56 +02:00
int64_t seek_timestamp ;
int seek_flags ;
2014-01-03 13:44:38 +02:00
int seek_stream_index ; /* into subdemuxer stream array */
2013-12-30 11:13:56 +02:00
2013-12-28 00:09:45 +02:00
/* Renditions associated with this playlist, if any.
* Alternative rendition playlists have a single rendition associated
* with them , and variant main Media Playlists may have
* multiple ( playlist - less ) renditions associated with them . */
int n_renditions ;
struct rendition * * renditions ;
2015-10-15 14:23:00 +03:00
/* Media Initialization Sections (EXT-X-MAP) associated with this
* playlist , if any . */
int n_init_sections ;
struct segment * * init_sections ;
2025-02-21 13:14:18 +01:00
int is_subtitle ; /* Indicates if it's a subtitle playlist */
2013-12-28 00:09:45 +02:00
} ;
/*
* Renditions are e . g . alternative subtitle or audio streams .
* The rendition may either be an external playlist or it may be
* contained in the main Media Playlist of the variant ( in which case
* playlist is NULL ) .
*/
struct rendition {
enum AVMediaType type ;
struct playlist * playlist ;
char group_id [ MAX_FIELD_LEN ] ;
char language [ MAX_FIELD_LEN ] ;
char name [ MAX_FIELD_LEN ] ;
int disposition ;
2010-08-19 14:54:37 +00:00
} ;
2013-12-27 13:01:10 +02:00
struct variant {
int bandwidth ;
2013-12-28 00:09:45 +02:00
/* every variant contains at least the main Media Playlist in index 0 */
2013-12-27 13:01:10 +02:00
int n_playlists ;
struct playlist * * playlists ;
2013-12-28 00:09:45 +02:00
char audio_group [ MAX_FIELD_LEN ] ;
char video_group [ MAX_FIELD_LEN ] ;
char subtitles_group [ MAX_FIELD_LEN ] ;
2013-12-27 13:01:10 +02:00
} ;
2012-02-14 11:50:51 +02:00
typedef struct HLSContext {
2015-03-28 18:27:35 -06:00
AVClass * class ;
2016-01-16 17:53:43 +01:00
AVFormatContext * ctx ;
2010-08-19 14:54:37 +00:00
int n_variants ;
struct variant * * variants ;
2013-12-27 13:01:10 +02:00
int n_playlists ;
struct playlist * * playlists ;
2013-12-28 00:09:45 +02:00
int n_renditions ;
struct rendition * * renditions ;
2013-12-27 13:01:10 +02:00
2021-01-16 11:40:36 +08:00
int64_t cur_seq_no ;
2019-11-18 17:37:00 +08:00
int m3u8_hold_counters ;
2015-03-28 18:27:35 -06:00
int live_start_index ;
2022-06-23 00:55:38 +08:00
int prefer_x_start ;
2011-03-21 00:21:56 +02:00
int first_packet ;
2011-11-15 18:35:03 +01:00
int64_t first_timestamp ;
2013-12-30 11:27:36 +02:00
int64_t cur_timestamp ;
2011-11-06 22:34:24 +02:00
AVIOInterruptCB * interrupt_callback ;
2015-07-23 21:11:58 +02:00
AVDictionary * avio_opts ;
2021-05-29 15:27:18 +05:30
AVDictionary * seg_format_opts ;
2017-06-03 21:20:04 +02:00
char * allowed_extensions ;
2025-04-30 01:37:27 +02:00
char * allowed_segment_extensions ;
2025-01-16 01:28:46 +01:00
int extension_picky ;
2017-08-26 01:26:58 +02:00
int max_reload ;
2017-10-04 14:52:52 -07:00
int http_persistent ;
2017-12-12 15:25:46 -08:00
int http_multiple ;
2019-08-08 00:12:16 +08:00
int http_seekable ;
2022-10-20 20:11:38 +08:00
int seg_max_retry ;
2017-10-04 14:52:52 -07:00
AVIOContext * playlist_pb ;
2021-09-22 00:12:00 +05:30
HLSCryptoContext crypto_ctx ;
2012-02-14 11:50:51 +02:00
} HLSContext ;
2010-08-19 14:54:37 +00:00
2018-04-23 17:30:21 -07:00
static void free_segment_dynarray ( struct segment * * segments , int n_segments )
2010-08-19 14:54:37 +00:00
{
int i ;
2018-04-23 17:30:21 -07:00
for ( i = 0 ; i < n_segments ; i + + ) {
av_freep ( & segments [ i ] - > key ) ;
av_freep ( & segments [ i ] - > url ) ;
av_freep ( & segments [ i ] ) ;
2014-04-11 08:14:40 +02:00
}
2018-04-23 17:30:21 -07:00
}
static void free_segment_list ( struct playlist * pls )
{
2019-01-14 23:42:09 +08:00
free_segment_dynarray ( pls - > segments , pls - > n_segments ) ;
av_freep ( & pls - > segments ) ;
pls - > n_segments = 0 ;
2013-12-27 13:01:10 +02:00
}
2015-10-15 14:23:00 +03:00
static void free_init_section_list ( struct playlist * pls )
{
int i ;
for ( i = 0 ; i < pls - > n_init_sections ; i + + ) {
2022-09-09 00:32:23 +02:00
av_freep ( & pls - > init_sections [ i ] - > key ) ;
2015-10-15 14:23:00 +03:00
av_freep ( & pls - > init_sections [ i ] - > url ) ;
av_freep ( & pls - > init_sections [ i ] ) ;
}
av_freep ( & pls - > init_sections ) ;
pls - > n_init_sections = 0 ;
}
2013-12-27 13:01:10 +02:00
static void free_playlist_list ( HLSContext * c )
{
int i ;
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
free_segment_list ( pls ) ;
2015-10-15 14:23:00 +03:00
free_init_section_list ( pls ) ;
2016-07-27 23:29:16 +03:00
av_freep ( & pls - > main_streams ) ;
2013-12-28 00:09:45 +02:00
av_freep ( & pls - > renditions ) ;
2014-04-05 14:47:26 +03:00
av_freep ( & pls - > id3_buf ) ;
av_dict_free ( & pls - > id3_initial ) ;
ff_id3v2_free_extra_meta ( & pls - > id3_deferred_extra ) ;
2015-10-15 14:23:00 +03:00
av_freep ( & pls - > init_sec_buf ) ;
2021-01-29 10:27:05 -03:00
av_packet_free ( & pls - > pkt ) ;
2021-08-04 16:52:07 +02:00
av_freep ( & pls - > pb . pub . buffer ) ;
2019-09-13 19:53:35 +08:00
ff_format_io_close ( c - > ctx , & pls - > input ) ;
2017-10-04 14:52:52 -07:00
pls - > input_read_done = 0 ;
2019-09-13 19:53:35 +08:00
ff_format_io_close ( c - > ctx , & pls - > input_next ) ;
2017-12-12 15:25:46 -08:00
pls - > input_next_requested = 0 ;
2013-12-27 13:01:10 +02:00
if ( pls - > ctx ) {
pls - > ctx - > pb = NULL ;
avformat_close_input ( & pls - > ctx ) ;
}
av_free ( pls ) ;
}
av_freep ( & c - > playlists ) ;
c - > n_playlists = 0 ;
2010-08-19 14:54:37 +00:00
}
2012-02-14 11:50:51 +02:00
static void free_variant_list ( HLSContext * c )
2010-08-19 14:54:37 +00:00
{
int i ;
for ( i = 0 ; i < c - > n_variants ; i + + ) {
struct variant * var = c - > variants [ i ] ;
2013-12-27 13:01:10 +02:00
av_freep ( & var - > playlists ) ;
2010-08-19 14:54:37 +00:00
av_free ( var ) ;
}
av_freep ( & c - > variants ) ;
c - > n_variants = 0 ;
}
2013-12-28 00:09:45 +02:00
static void free_rendition_list ( HLSContext * c )
{
int i ;
for ( i = 0 ; i < c - > n_renditions ; i + + )
2014-10-07 04:29:27 +02:00
av_freep ( & c - > renditions [ i ] ) ;
2013-12-28 00:09:45 +02:00
av_freep ( & c - > renditions ) ;
c - > n_renditions = 0 ;
}
2013-12-27 13:01:10 +02:00
static struct playlist * new_playlist ( HLSContext * c , const char * url ,
const char * base )
{
struct playlist * pls = av_mallocz ( sizeof ( struct playlist ) ) ;
if ( ! pls )
return NULL ;
2021-01-29 10:27:05 -03:00
pls - > pkt = av_packet_alloc ( ) ;
if ( ! pls - > pkt ) {
av_free ( pls ) ;
return NULL ;
}
2013-12-27 13:01:10 +02:00
ff_make_absolute_url ( pls - > url , sizeof ( pls - > url ) , base , url ) ;
2020-08-14 22:06:54 +02:00
if ( ! pls - > url [ 0 ] ) {
2021-01-29 10:27:05 -03:00
av_packet_free ( & pls - > pkt ) ;
2020-08-14 22:06:54 +02:00
av_free ( pls ) ;
2020-05-28 10:41:26 +08:00
return NULL ;
2020-08-14 22:06:54 +02:00
}
2013-12-30 11:13:56 +02:00
pls - > seek_timestamp = AV_NOPTS_VALUE ;
2014-04-05 14:47:26 +03:00
pls - > is_id3_timestamped = - 1 ;
pls - > id3_mpegts_timestamp = AV_NOPTS_VALUE ;
2013-12-27 13:01:10 +02:00
dynarray_add ( & c - > playlists , & c - > n_playlists , pls ) ;
return pls ;
}
2013-12-28 00:09:45 +02:00
struct variant_info {
char bandwidth [ 20 ] ;
/* variant group ids: */
char audio [ MAX_FIELD_LEN ] ;
char video [ MAX_FIELD_LEN ] ;
char subtitles [ MAX_FIELD_LEN ] ;
} ;
static struct variant * new_variant ( HLSContext * c , struct variant_info * info ,
2010-08-19 14:54:37 +00:00
const char * url , const char * base )
{
2013-12-27 13:01:10 +02:00
struct variant * var ;
struct playlist * pls ;
pls = new_playlist ( c , url , base ) ;
if ( ! pls )
return NULL ;
var = av_mallocz ( sizeof ( struct variant ) ) ;
2010-08-19 14:54:37 +00:00
if ( ! var )
return NULL ;
2013-12-27 13:01:10 +02:00
2013-12-28 00:09:45 +02:00
if ( info ) {
var - > bandwidth = atoi ( info - > bandwidth ) ;
strcpy ( var - > audio_group , info - > audio ) ;
strcpy ( var - > video_group , info - > video ) ;
strcpy ( var - > subtitles_group , info - > subtitles ) ;
}
2010-08-19 14:54:37 +00:00
dynarray_add ( & c - > variants , & c - > n_variants , var ) ;
2013-12-27 13:01:10 +02:00
dynarray_add ( & var - > playlists , & var - > n_playlists , pls ) ;
2010-08-19 14:54:37 +00:00
return var ;
}
static void handle_variant_args ( struct variant_info * info , const char * key ,
int key_len , char * * dest , int * dest_len )
{
2011-01-23 23:39:02 +02:00
if ( ! strncmp ( key , " BANDWIDTH= " , key_len ) ) {
2010-08-19 14:54:37 +00:00
* dest = info - > bandwidth ;
* dest_len = sizeof ( info - > bandwidth ) ;
2013-12-28 00:09:45 +02:00
} else if ( ! strncmp ( key , " AUDIO= " , key_len ) ) {
* dest = info - > audio ;
* dest_len = sizeof ( info - > audio ) ;
} else if ( ! strncmp ( key , " VIDEO= " , key_len ) ) {
* dest = info - > video ;
* dest_len = sizeof ( info - > video ) ;
} else if ( ! strncmp ( key , " SUBTITLES= " , key_len ) ) {
* dest = info - > subtitles ;
* dest_len = sizeof ( info - > subtitles ) ;
2010-08-19 14:54:37 +00:00
}
}
2011-01-23 23:42:18 +02:00
struct key_info {
char uri [ MAX_URL_SIZE ] ;
2015-03-05 19:49:57 +01:00
char method [ 11 ] ;
2011-01-23 23:42:18 +02:00
char iv [ 35 ] ;
} ;
static void handle_key_args ( struct key_info * info , const char * key ,
int key_len , char * * dest , int * dest_len )
{
if ( ! strncmp ( key , " METHOD= " , key_len ) ) {
* dest = info - > method ;
* dest_len = sizeof ( info - > method ) ;
} else if ( ! strncmp ( key , " URI= " , key_len ) ) {
* dest = info - > uri ;
* dest_len = sizeof ( info - > uri ) ;
} else if ( ! strncmp ( key , " IV= " , key_len ) ) {
* dest = info - > iv ;
* dest_len = sizeof ( info - > iv ) ;
}
}
2015-10-15 14:23:00 +03:00
struct init_section_info {
char uri [ MAX_URL_SIZE ] ;
char byterange [ 32 ] ;
} ;
static struct segment * new_init_section ( struct playlist * pls ,
struct init_section_info * info ,
const char * url_base )
{
struct segment * sec ;
2020-03-03 03:41:13 +01:00
char tmp_str [ MAX_URL_SIZE ] , * ptr = tmp_str ;
2015-10-15 14:23:00 +03:00
if ( ! info - > uri [ 0 ] )
return NULL ;
sec = av_mallocz ( sizeof ( * sec ) ) ;
if ( ! sec )
return NULL ;
2019-09-24 11:18:59 +08:00
if ( ! av_strncasecmp ( info - > uri , " data: " , 5 ) ) {
2020-03-03 03:41:13 +01:00
ptr = info - > uri ;
2019-09-24 11:18:59 +08:00
} else {
ff_make_absolute_url ( tmp_str , sizeof ( tmp_str ) , url_base , info - > uri ) ;
2020-05-28 10:41:26 +08:00
if ( ! tmp_str [ 0 ] ) {
av_free ( sec ) ;
return NULL ;
}
2019-09-24 11:18:59 +08:00
}
2020-03-03 03:41:13 +01:00
sec - > url = av_strdup ( ptr ) ;
2015-10-15 14:23:00 +03:00
if ( ! sec - > url ) {
av_free ( sec ) ;
return NULL ;
}
if ( info - > byterange [ 0 ] ) {
2016-09-24 09:29:03 +03:00
sec - > size = strtoll ( info - > byterange , NULL , 10 ) ;
2015-10-15 14:23:00 +03:00
ptr = strchr ( info - > byterange , ' @ ' ) ;
if ( ptr )
2016-09-24 09:29:03 +03:00
sec - > url_offset = strtoll ( ptr + 1 , NULL , 10 ) ;
2015-10-15 14:23:00 +03:00
} else {
/* the entire file is the init section */
sec - > size = - 1 ;
}
dynarray_add ( & pls - > init_sections , & pls - > n_init_sections , sec ) ;
return sec ;
}
2025-08-05 19:26:50 +02:00
static void handle_init_section_args ( void * context , const char * key ,
int key_len , char * * dest , int * dest_len )
2015-10-15 14:23:00 +03:00
{
2025-08-05 19:26:50 +02:00
struct init_section_info * info = context ;
2015-10-15 14:23:00 +03:00
if ( ! strncmp ( key , " URI= " , key_len ) ) {
* dest = info - > uri ;
* dest_len = sizeof ( info - > uri ) ;
} else if ( ! strncmp ( key , " BYTERANGE= " , key_len ) ) {
* dest = info - > byterange ;
* dest_len = sizeof ( info - > byterange ) ;
}
}
2013-12-28 00:09:45 +02:00
struct rendition_info {
char type [ 16 ] ;
char uri [ MAX_URL_SIZE ] ;
char group_id [ MAX_FIELD_LEN ] ;
char language [ MAX_FIELD_LEN ] ;
char assoc_language [ MAX_FIELD_LEN ] ;
char name [ MAX_FIELD_LEN ] ;
char defaultr [ 4 ] ;
char forced [ 4 ] ;
char characteristics [ MAX_CHARACTERISTICS_LEN ] ;
} ;
static struct rendition * new_rendition ( HLSContext * c , struct rendition_info * info ,
const char * url_base )
{
struct rendition * rend ;
enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN ;
char * characteristic ;
char * chr_ptr ;
char * saveptr ;
if ( ! strcmp ( info - > type , " AUDIO " ) )
type = AVMEDIA_TYPE_AUDIO ;
else if ( ! strcmp ( info - > type , " VIDEO " ) )
type = AVMEDIA_TYPE_VIDEO ;
else if ( ! strcmp ( info - > type , " SUBTITLES " ) )
type = AVMEDIA_TYPE_SUBTITLE ;
else if ( ! strcmp ( info - > type , " CLOSED-CAPTIONS " ) )
/* CLOSED-CAPTIONS is ignored since we do not support CEA-608 CC in
* AVC SEI RBSP anyway */
return NULL ;
2019-06-01 18:45:35 +08:00
if ( type = = AVMEDIA_TYPE_UNKNOWN ) {
2019-10-27 10:00:24 +08:00
av_log ( c - > ctx , AV_LOG_WARNING , " Can't support the type: %s \n " , info - > type ) ;
2013-12-28 00:09:45 +02:00
return NULL ;
2019-06-01 18:45:35 +08:00
}
2013-12-28 00:09:45 +02:00
/* URI is mandatory for subtitles as per spec */
2019-06-01 18:45:35 +08:00
if ( type = = AVMEDIA_TYPE_SUBTITLE & & ! info - > uri [ 0 ] ) {
2019-10-27 10:00:24 +08:00
av_log ( c - > ctx , AV_LOG_ERROR , " The URI tag is REQUIRED for subtitle. \n " ) ;
2013-12-28 00:09:45 +02:00
return NULL ;
2019-06-01 18:45:35 +08:00
}
2013-12-28 00:09:45 +02:00
rend = av_mallocz ( sizeof ( struct rendition ) ) ;
if ( ! rend )
return NULL ;
dynarray_add ( & c - > renditions , & c - > n_renditions , rend ) ;
rend - > type = type ;
strcpy ( rend - > group_id , info - > group_id ) ;
strcpy ( rend - > language , info - > language ) ;
strcpy ( rend - > name , info - > name ) ;
/* add the playlist if this is an external rendition */
if ( info - > uri [ 0 ] ) {
rend - > playlist = new_playlist ( c , info - > uri , url_base ) ;
2025-02-21 13:14:18 +01:00
if ( rend - > playlist ) {
if ( type = = AVMEDIA_TYPE_SUBTITLE ) {
rend - > playlist - > is_subtitle = 1 ;
rend - > playlist - > is_id3_timestamped = 0 ;
}
2013-12-28 00:09:45 +02:00
dynarray_add ( & rend - > playlist - > renditions ,
& rend - > playlist - > n_renditions , rend ) ;
2025-02-21 13:14:18 +01:00
}
2013-12-28 00:09:45 +02:00
}
if ( info - > assoc_language [ 0 ] ) {
2023-11-23 10:31:59 -05:00
size_t langlen = strlen ( rend - > language ) ;
2013-12-28 00:09:45 +02:00
if ( langlen < sizeof ( rend - > language ) - 3 ) {
2023-11-23 10:31:59 -05:00
size_t assoc_len ;
2013-12-28 00:09:45 +02:00
rend - > language [ langlen ] = ' , ' ;
2023-11-23 10:31:59 -05:00
assoc_len = av_strlcpy ( rend - > language + langlen + 1 ,
info - > assoc_language ,
sizeof ( rend - > language ) - langlen - 1 ) ;
if ( langlen + assoc_len + 2 > sizeof ( rend - > language ) ) // truncation occurred
av_log ( c - > ctx , AV_LOG_WARNING , " Truncated rendition language: %s \n " ,
info - > assoc_language ) ;
2013-12-28 00:09:45 +02:00
}
}
if ( ! strcmp ( info - > defaultr , " YES " ) )
rend - > disposition | = AV_DISPOSITION_DEFAULT ;
if ( ! strcmp ( info - > forced , " YES " ) )
rend - > disposition | = AV_DISPOSITION_FORCED ;
chr_ptr = info - > characteristics ;
while ( ( characteristic = av_strtok ( chr_ptr , " , " , & saveptr ) ) ) {
if ( ! strcmp ( characteristic , " public.accessibility.describes-music-and-sound " ) )
rend - > disposition | = AV_DISPOSITION_HEARING_IMPAIRED ;
else if ( ! strcmp ( characteristic , " public.accessibility.describes-video " ) )
rend - > disposition | = AV_DISPOSITION_VISUAL_IMPAIRED ;
chr_ptr = NULL ;
}
return rend ;
}
static void handle_rendition_args ( struct rendition_info * info , const char * key ,
int key_len , char * * dest , int * dest_len )
{
if ( ! strncmp ( key , " TYPE= " , key_len ) ) {
* dest = info - > type ;
* dest_len = sizeof ( info - > type ) ;
} else if ( ! strncmp ( key , " URI= " , key_len ) ) {
* dest = info - > uri ;
* dest_len = sizeof ( info - > uri ) ;
} else if ( ! strncmp ( key , " GROUP-ID= " , key_len ) ) {
* dest = info - > group_id ;
* dest_len = sizeof ( info - > group_id ) ;
} else if ( ! strncmp ( key , " LANGUAGE= " , key_len ) ) {
* dest = info - > language ;
* dest_len = sizeof ( info - > language ) ;
} else if ( ! strncmp ( key , " ASSOC-LANGUAGE= " , key_len ) ) {
* dest = info - > assoc_language ;
* dest_len = sizeof ( info - > assoc_language ) ;
} else if ( ! strncmp ( key , " NAME= " , key_len ) ) {
* dest = info - > name ;
* dest_len = sizeof ( info - > name ) ;
} else if ( ! strncmp ( key , " DEFAULT= " , key_len ) ) {
* dest = info - > defaultr ;
* dest_len = sizeof ( info - > defaultr ) ;
} else if ( ! strncmp ( key , " FORCED= " , key_len ) ) {
* dest = info - > forced ;
* dest_len = sizeof ( info - > forced ) ;
} else if ( ! strncmp ( key , " CHARACTERISTICS= " , key_len ) ) {
* dest = info - > characteristics ;
* dest_len = sizeof ( info - > characteristics ) ;
}
/*
* ignored :
* - AUTOSELECT : client may autoselect based on e . g . system language
* - INSTREAM - ID : EIA - 608 closed caption number ( " CC1 " . . " CC4 " )
*/
}
2014-01-03 09:47:26 +02:00
/* used by parse_playlist to allocate a new variant+playlist when the
* playlist is detected to be a Media Playlist ( not Master Playlist )
* and we have no parent Master Playlist ( parsing of which would have
2015-08-20 19:27:24 -04:00
* allocated the variant and playlist already )
* * pls = = NULL = > Master Playlist or parentless Media Playlist
* * pls ! = NULL = > parented Media Playlist , playlist + variant allocated */
2014-01-03 09:47:26 +02:00
static int ensure_playlist ( HLSContext * c , struct playlist * * pls , const char * url )
{
if ( * pls )
return 0 ;
if ( ! new_variant ( c , NULL , url , NULL ) )
return AVERROR ( ENOMEM ) ;
* pls = c - > playlists [ c - > n_playlists - 1 ] ;
return 0 ;
}
2017-10-04 14:52:52 -07:00
static int open_url_keepalive ( AVFormatContext * s , AVIOContext * * pb ,
2019-10-04 22:56:02 +01:00
const char * url , AVDictionary * * options )
2017-10-04 14:52:52 -07:00
{
2017-12-25 11:33:06 -08:00
# if !CONFIG_HTTP_PROTOCOL
return AVERROR_PROTOCOL_NOT_FOUND ;
# else
2017-10-04 14:52:52 -07:00
int ret ;
URLContext * uc = ffio_geturlcontext ( * pb ) ;
av_assert0 ( uc ) ;
( * pb ) - > eof_reached = 0 ;
2019-10-04 22:56:02 +01:00
ret = ff_http_do_new_request2 ( uc , url , options ) ;
2017-10-04 14:52:52 -07:00
if ( ret < 0 ) {
ff_format_io_close ( s , pb ) ;
}
return ret ;
2017-12-25 11:33:06 -08:00
# endif
2017-10-04 14:52:52 -07:00
}
2016-01-20 11:43:18 +01:00
static int open_url ( AVFormatContext * s , AVIOContext * * pb , const char * url ,
2020-09-07 20:49:02 +02:00
AVDictionary * * opts , AVDictionary * opts2 , int * is_http_out )
2015-07-23 21:11:58 +02:00
{
2016-02-16 16:26:49 +00:00
HLSContext * c = s - > priv_data ;
2015-07-23 21:11:58 +02:00
AVDictionary * tmp = NULL ;
2016-03-14 12:19:31 +01:00
const char * proto_name = NULL ;
2015-07-23 21:11:58 +02:00
int ret ;
2017-12-26 14:29:03 -08:00
int is_http = 0 ;
2015-07-23 21:11:58 +02:00
2016-03-14 12:19:31 +01:00
if ( av_strstart ( url , " crypto " , NULL ) ) {
if ( url [ 6 ] = = ' + ' | | url [ 6 ] = = ' : ' )
proto_name = avio_find_protocol_name ( url + 7 ) ;
2019-09-24 11:18:59 +08:00
} else if ( av_strstart ( url , " data " , NULL ) ) {
if ( url [ 4 ] = = ' + ' | | url [ 4 ] = = ' : ' )
proto_name = avio_find_protocol_name ( url + 5 ) ;
2016-03-14 12:19:31 +01:00
}
if ( ! proto_name )
proto_name = avio_find_protocol_name ( url ) ;
2016-01-15 15:29:22 +01:00
if ( ! proto_name )
return AVERROR_INVALIDDATA ;
2016-01-13 11:51:12 +03:00
// only http(s) & file are allowed
2017-06-03 21:20:04 +02:00
if ( av_strstart ( proto_name , " file " , NULL ) ) {
if ( strcmp ( c - > allowed_extensions , " ALL " ) & & ! av_match_ext ( url , c - > allowed_extensions ) ) {
av_log ( s , AV_LOG_ERROR ,
" Filename extension of \' %s \' is not a common multimedia extension, blocked for security reasons. \n "
" If you wish to override this adjust allowed_extensions, you can set it to \' ALL \' to allow all \n " ,
url ) ;
return AVERROR_INVALIDDATA ;
}
} else if ( av_strstart ( proto_name , " http " , NULL ) ) {
2017-12-26 14:29:03 -08:00
is_http = 1 ;
2019-09-24 11:18:59 +08:00
} else if ( av_strstart ( proto_name , " data " , NULL ) ) {
;
2017-06-03 21:20:04 +02:00
} else
2016-01-13 11:51:12 +03:00
return AVERROR_INVALIDDATA ;
2017-06-03 21:20:04 +02:00
2016-01-15 13:29:38 +01:00
if ( ! strncmp ( proto_name , url , strlen ( proto_name ) ) & & url [ strlen ( proto_name ) ] = = ' : ' )
;
2016-03-14 12:19:31 +01:00
else if ( av_strstart ( url , " crypto " , NULL ) & & ! strncmp ( proto_name , url + 7 , strlen ( proto_name ) ) & & url [ 7 + strlen ( proto_name ) ] = = ' : ' )
;
2019-09-24 11:18:59 +08:00
else if ( av_strstart ( url , " data " , NULL ) & & ! strncmp ( proto_name , url + 5 , strlen ( proto_name ) ) & & url [ 5 + strlen ( proto_name ) ] = = ' : ' )
;
2016-01-15 15:29:22 +01:00
else if ( strcmp ( proto_name , " file " ) | | ! strncmp ( url , " file, " , 5 ) )
2016-01-15 13:29:38 +01:00
return AVERROR_INVALIDDATA ;
2015-07-23 21:11:58 +02:00
2020-09-07 20:49:02 +02:00
av_dict_copy ( & tmp , * opts , 0 ) ;
2019-09-13 19:53:37 +08:00
av_dict_copy ( & tmp , opts2 , 0 ) ;
2017-12-26 14:29:03 -08:00
if ( is_http & & c - > http_persistent & & * pb ) {
2019-10-04 22:56:02 +01:00
ret = open_url_keepalive ( c - > ctx , pb , url , & tmp ) ;
2017-10-04 14:52:52 -07:00
if ( ret = = AVERROR_EXIT ) {
2019-09-13 19:53:37 +08:00
av_dict_free ( & tmp ) ;
2017-10-04 14:52:52 -07:00
return ret ;
} else if ( ret < 0 ) {
2017-12-22 16:30:42 -08:00
if ( ret ! = AVERROR_EOF )
av_log ( s , AV_LOG_WARNING ,
2019-09-13 19:53:34 +08:00
" keepalive request failed for '%s' with error: '%s' when opening url, retrying with new connection \n " ,
2017-12-22 16:30:42 -08:00
url , av_err2str ( ret ) ) ;
2021-03-10 21:43:10 +00:00
av_dict_copy ( & tmp , * opts , 0 ) ;
av_dict_copy ( & tmp , opts2 , 0 ) ;
2017-10-04 14:52:52 -07:00
ret = s - > io_open ( s , pb , url , AVIO_FLAG_READ , & tmp ) ;
}
} else {
ret = s - > io_open ( s , pb , url , AVIO_FLAG_READ , & tmp ) ;
}
2016-02-16 16:26:49 +00:00
if ( ret > = 0 ) {
2015-09-25 12:11:52 -03:00
// update cookies on http response with setcookies.
2017-05-16 21:37:31 -04:00
char * new_cookies = NULL ;
if ( ! ( s - > flags & AVFMT_FLAG_CUSTOM_IO ) )
av_opt_get ( * pb , " cookies " , AV_OPT_SEARCH_CHILDREN , ( uint8_t * * ) & new_cookies ) ;
2018-04-17 14:40:06 +08:00
if ( new_cookies )
2020-09-07 20:49:02 +02:00
av_dict_set ( opts , " cookies " , new_cookies , AV_DICT_DONT_STRDUP_VAL ) ;
2015-09-25 12:11:52 -03:00
}
2015-07-23 21:11:58 +02:00
av_dict_free ( & tmp ) ;
2017-12-26 14:29:03 -08:00
if ( is_http_out )
* is_http_out = is_http ;
2016-07-26 15:18:40 +03:00
2015-07-23 21:11:58 +02:00
return ret ;
}
2025-01-16 01:28:46 +01:00
static int test_segment ( AVFormatContext * s , const AVInputFormat * in_fmt , struct playlist * pls , struct segment * seg )
{
HLSContext * c = s - > priv_data ;
int matchA = 3 ;
int matchF = 0 ;
if ( ! c - > extension_picky )
return 0 ;
2025-04-30 01:37:27 +02:00
if ( strcmp ( c - > allowed_segment_extensions , " ALL " ) )
matchA = av_match_ext ( seg - > url , c - > allowed_segment_extensions )
+ 2 * ( ff_match_url_ext ( seg - > url , c - > allowed_segment_extensions ) > 0 ) ;
2025-01-16 01:28:46 +01:00
if ( ! matchA ) {
2025-04-30 01:37:27 +02:00
av_log ( s , AV_LOG_ERROR , " URL %s is not in allowed_segment_extensions, consider updating hls.c and submitting a patch to ffmpeg-devel, if this should be added \n " , seg - > url ) ;
2025-01-16 01:28:46 +01:00
return AVERROR_INVALIDDATA ;
}
if ( in_fmt ) {
if ( in_fmt - > extensions ) {
matchF = av_match_ext ( seg - > url , in_fmt - > extensions )
+ 2 * ( ff_match_url_ext ( seg - > url , in_fmt - > extensions ) > 0 ) ;
2025-04-06 18:52:05 +02:00
// Youtube uses aac files with .ts extension
if ( av_match_name ( " mp4 " , in_fmt - > name ) | | av_match_name ( " aac " , in_fmt - > name ) ) {
2025-08-02 18:55:26 +02:00
matchF | = av_match_ext ( seg - > url , " ts,m2t,m2ts,mts,mpg,m4s,mpeg,mpegts,cmfv,cmfa " )
+ 2 * ( ff_match_url_ext ( seg - > url , " ts,m2t,m2ts,mts,mpg,m4s,mpeg,mpegts,cmfv,cmfa " ) > 0 ) ;
2025-01-28 13:26:34 +01:00
}
2025-01-28 23:07:54 +01:00
} else if ( ! strcmp ( in_fmt - > name , " mpegts " ) ) {
2025-04-30 01:05:18 +02:00
const char * str = " ts,m2t,m2ts,mts,mpg,m4s,mpeg,mpegts "
" ,html " // https://flash1.bogulus.cfd/
;
matchF = av_match_ext ( seg - > url , str )
+ 2 * ( ff_match_url_ext ( seg - > url , str ) > 0 ) ;
2025-02-21 13:14:18 +01:00
} else if ( ! strcmp ( in_fmt - > name , " webvtt " ) ) {
matchF = av_match_ext ( seg - > url , " vtt,webvtt " )
+ 2 * ( ff_match_url_ext ( seg - > url , " vtt,webvtt " ) > 0 ) ;
2025-01-28 23:07:54 +01:00
}
2025-01-16 01:28:46 +01:00
if ( ! ( matchA & matchF ) ) {
2025-01-28 12:51:58 +01:00
av_log ( s , AV_LOG_ERROR , " detected format %s extension %s mismatches allowed extensions in url %s \n " , in_fmt - > name , in_fmt - > extensions ? in_fmt - > extensions : " none " , seg - > url ) ;
2025-01-16 01:28:46 +01:00
return AVERROR_INVALIDDATA ;
}
}
return 0 ;
}
2012-02-14 11:50:51 +02:00
static int parse_playlist ( HLSContext * c , const char * url ,
2013-12-27 13:01:10 +02:00
struct playlist * pls , AVIOContext * in )
2010-08-19 14:54:37 +00:00
{
2013-12-28 00:09:45 +02:00
int ret = 0 , is_segment = 0 , is_variant = 0 ;
2013-07-29 10:26:02 +03:00
int64_t duration = 0 ;
2011-01-23 23:42:18 +02:00
enum KeyType key_type = KEY_NONE ;
uint8_t iv [ 16 ] = " " ;
int has_iv = 0 ;
2011-12-14 20:33:39 +02:00
char key [ MAX_URL_SIZE ] = " " ;
2013-09-21 20:59:30 +02:00
char line [ MAX_URL_SIZE ] ;
2010-08-19 14:54:37 +00:00
const char * ptr ;
int close_in = 0 ;
2013-12-28 09:42:46 +02:00
int64_t seg_offset = 0 ;
int64_t seg_size = - 1 ;
2013-11-21 11:31:53 +02:00
uint8_t * new_url = NULL ;
2013-12-28 00:09:45 +02:00
struct variant_info variant_info ;
2014-04-11 08:14:40 +02:00
char tmp_str [ MAX_URL_SIZE ] ;
2015-10-15 14:23:00 +03:00
struct segment * cur_init_section = NULL ;
2017-12-25 18:25:13 -08:00
int is_http = av_strstart ( url , " http " , NULL ) ;
2018-04-23 17:30:21 -07:00
struct segment * * prev_segments = NULL ;
int prev_n_segments = 0 ;
2021-01-16 11:40:36 +08:00
int64_t prev_start_seq_no = - 1 ;
2010-08-19 14:54:37 +00:00
2017-12-25 18:25:13 -08:00
if ( is_http & & ! in & & c - > http_persistent & & c - > playlist_pb ) {
2017-10-04 14:52:52 -07:00
in = c - > playlist_pb ;
2019-10-04 22:56:02 +01:00
ret = open_url_keepalive ( c - > ctx , & c - > playlist_pb , url , NULL ) ;
2017-10-04 14:52:52 -07:00
if ( ret = = AVERROR_EXIT ) {
return ret ;
} else if ( ret < 0 ) {
2017-12-22 16:30:42 -08:00
if ( ret ! = AVERROR_EOF )
av_log ( c - > ctx , AV_LOG_WARNING ,
2019-09-13 19:53:34 +08:00
" keepalive request failed for '%s' with error: '%s' when parsing playlist \n " ,
2017-12-22 16:30:42 -08:00
url , av_err2str ( ret ) ) ;
2017-10-04 14:52:52 -07:00
in = NULL ;
}
}
2010-08-19 14:54:37 +00:00
if ( ! in ) {
2012-10-02 22:22:44 +01:00
AVDictionary * opts = NULL ;
2018-04-17 14:40:06 +08:00
av_dict_copy ( & opts , c - > avio_opts , 0 ) ;
2013-01-22 21:09:57 -05:00
2017-10-04 14:52:52 -07:00
if ( c - > http_persistent )
av_dict_set ( & opts , " multiple_requests " , " 1 " , 0 ) ;
2016-02-10 14:40:32 +00:00
ret = c - > ctx - > io_open ( c - > ctx , & in , url , AVIO_FLAG_READ , & opts ) ;
2012-10-02 22:22:44 +01:00
av_dict_free ( & opts ) ;
if ( ret < 0 )
2010-08-19 14:54:37 +00:00
return ret ;
2017-10-04 14:52:52 -07:00
2017-12-25 18:25:13 -08:00
if ( is_http & & c - > http_persistent )
2017-10-04 14:52:52 -07:00
c - > playlist_pb = in ;
else
close_in = 1 ;
2010-08-19 14:54:37 +00:00
}
2013-11-21 11:31:53 +02:00
if ( av_opt_get ( in , " location " , AV_OPT_SEARCH_CHILDREN , & new_url ) > = 0 )
url = new_url ;
2018-04-09 23:11:02 +08:00
ff_get_chomp_line ( in , line , sizeof ( line ) ) ;
2010-08-19 14:54:37 +00:00
if ( strcmp ( line , " #EXTM3U " ) ) {
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
2013-12-27 13:01:10 +02:00
if ( pls ) {
2018-04-23 17:30:21 -07:00
prev_start_seq_no = pls - > start_seq_no ;
prev_segments = pls - > segments ;
prev_n_segments = pls - > n_segments ;
pls - > segments = NULL ;
pls - > n_segments = 0 ;
2013-12-27 13:01:10 +02:00
pls - > finished = 0 ;
2014-01-05 02:18:12 +04:00
pls - > type = PLS_TYPE_UNSPECIFIED ;
2011-03-21 00:08:59 +02:00
}
2014-08-07 17:12:41 -03:00
while ( ! avio_feof ( in ) ) {
2018-04-09 23:11:02 +08:00
ff_get_chomp_line ( in , line , sizeof ( line ) ) ;
2010-08-19 14:54:37 +00:00
if ( av_strstart ( line , " #EXT-X-STREAM-INF: " , & ptr ) ) {
is_variant = 1 ;
2013-12-28 00:09:45 +02:00
memset ( & variant_info , 0 , sizeof ( variant_info ) ) ;
2010-08-19 14:54:37 +00:00
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_variant_args ,
2013-12-28 00:09:45 +02:00
& variant_info ) ;
2011-01-23 23:42:18 +02:00
} else if ( av_strstart ( line , " #EXT-X-KEY: " , & ptr ) ) {
struct key_info info = { { 0 } } ;
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_key_args ,
& info ) ;
key_type = KEY_NONE ;
has_iv = 0 ;
if ( ! strcmp ( info . method , " AES-128 " ) )
key_type = KEY_AES_128 ;
2015-03-05 19:49:57 +01:00
if ( ! strcmp ( info . method , " SAMPLE-AES " ) )
key_type = KEY_SAMPLE_AES ;
2021-05-07 19:05:13 +08:00
if ( ! av_strncasecmp ( info . iv , " 0x " , 2 ) ) {
2011-01-23 23:42:18 +02:00
ff_hex_to_data ( iv , info . iv + 2 ) ;
has_iv = 1 ;
}
av_strlcpy ( key , info . uri , sizeof ( key ) ) ;
2013-12-28 00:09:45 +02:00
} else if ( av_strstart ( line , " #EXT-X-MEDIA: " , & ptr ) ) {
struct rendition_info info = { { 0 } } ;
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_rendition_args ,
& info ) ;
new_rendition ( c , & info , url ) ;
2010-08-19 14:54:37 +00:00
} else if ( av_strstart ( line , " #EXT-X-TARGETDURATION: " , & ptr ) ) {
2022-03-20 22:54:31 +01:00
int64_t t ;
2014-01-03 09:47:26 +02:00
ret = ensure_playlist ( c , & pls , url ) ;
if ( ret < 0 )
goto fail ;
2022-03-20 22:54:31 +01:00
t = strtoll ( ptr , NULL , 10 ) ;
if ( t < 0 | | t > = INT64_MAX / AV_TIME_BASE ) {
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
pls - > target_duration = t * AV_TIME_BASE ;
2010-08-19 14:54:37 +00:00
} else if ( av_strstart ( line , " #EXT-X-MEDIA-SEQUENCE: " , & ptr ) ) {
2021-01-16 11:40:36 +08:00
uint64_t seq_no ;
2014-01-03 09:47:26 +02:00
ret = ensure_playlist ( c , & pls , url ) ;
if ( ret < 0 )
goto fail ;
2021-01-16 11:40:36 +08:00
seq_no = strtoull ( ptr , NULL , 10 ) ;
2022-06-16 23:02:11 +02:00
if ( seq_no > INT64_MAX / 2 ) {
2021-01-16 11:40:36 +08:00
av_log ( c - > ctx , AV_LOG_DEBUG , " MEDIA-SEQUENCE higher than "
2022-06-16 23:02:11 +02:00
" INT64_MAX/2, mask out the highest bit \n " ) ;
seq_no & = INT64_MAX / 2 ;
2021-01-16 11:40:36 +08:00
}
pls - > start_seq_no = seq_no ;
2014-01-05 02:18:12 +04:00
} else if ( av_strstart ( line , " #EXT-X-PLAYLIST-TYPE: " , & ptr ) ) {
ret = ensure_playlist ( c , & pls , url ) ;
if ( ret < 0 )
goto fail ;
if ( ! strcmp ( ptr , " EVENT " ) )
pls - > type = PLS_TYPE_EVENT ;
else if ( ! strcmp ( ptr , " VOD " ) )
pls - > type = PLS_TYPE_VOD ;
2015-10-15 14:23:00 +03:00
} else if ( av_strstart ( line , " #EXT-X-MAP: " , & ptr ) ) {
struct init_section_info info = { { 0 } } ;
ret = ensure_playlist ( c , & pls , url ) ;
if ( ret < 0 )
goto fail ;
2025-08-05 19:26:50 +02:00
ff_parse_key_value ( ptr , handle_init_section_args , & info ) ;
2015-10-15 14:23:00 +03:00
cur_init_section = new_init_section ( pls , & info , url ) ;
2021-04-10 11:59:00 +02:00
if ( ! cur_init_section ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2018-08-13 18:15:23 +08:00
cur_init_section - > key_type = key_type ;
if ( has_iv ) {
memcpy ( cur_init_section - > iv , iv , sizeof ( iv ) ) ;
} else {
2021-01-16 11:40:36 +08:00
int64_t seq = pls - > start_seq_no + pls - > n_segments ;
2018-08-13 18:15:23 +08:00
memset ( cur_init_section - > iv , 0 , sizeof ( cur_init_section - > iv ) ) ;
2021-01-16 11:40:36 +08:00
AV_WB64 ( cur_init_section - > iv + 8 , seq ) ;
2018-08-13 18:15:23 +08:00
}
if ( key_type ! = KEY_NONE ) {
ff_make_absolute_url ( tmp_str , sizeof ( tmp_str ) , url , key ) ;
2020-05-28 10:41:26 +08:00
if ( ! tmp_str [ 0 ] ) {
av_free ( cur_init_section ) ;
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
2018-08-13 18:15:23 +08:00
cur_init_section - > key = av_strdup ( tmp_str ) ;
if ( ! cur_init_section - > key ) {
av_free ( cur_init_section ) ;
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
} else {
cur_init_section - > key = NULL ;
}
2022-06-23 00:55:38 +08:00
} else if ( av_strstart ( line , " #EXT-X-START: " , & ptr ) ) {
const char * time_offset_value = NULL ;
ret = ensure_playlist ( c , & pls , url ) ;
if ( ret < 0 ) {
goto fail ;
}
if ( av_strstart ( ptr , " TIME-OFFSET= " , & time_offset_value ) ) {
float offset = strtof ( time_offset_value , NULL ) ;
pls - > start_time_offset = offset * AV_TIME_BASE ;
pls - > time_offset_flag = 1 ;
} else {
av_log ( c - > ctx , AV_LOG_WARNING , " #EXT-X-START value is "
" invalid, it will be ignored " ) ;
continue ;
}
2010-08-19 14:54:37 +00:00
} else if ( av_strstart ( line , " #EXT-X-ENDLIST " , & ptr ) ) {
2013-12-27 13:01:10 +02:00
if ( pls )
pls - > finished = 1 ;
2010-08-19 14:54:37 +00:00
} else if ( av_strstart ( line , " #EXTINF: " , & ptr ) ) {
is_segment = 1 ;
2013-07-29 10:26:02 +03:00
duration = atof ( ptr ) * AV_TIME_BASE ;
2013-12-28 09:42:46 +02:00
} else if ( av_strstart ( line , " #EXT-X-BYTERANGE: " , & ptr ) ) {
2016-09-24 09:29:03 +03:00
seg_size = strtoll ( ptr , NULL , 10 ) ;
2013-12-28 09:42:46 +02:00
ptr = strchr ( ptr , ' @ ' ) ;
if ( ptr )
2016-09-24 09:29:03 +03:00
seg_offset = strtoll ( ptr + 1 , NULL , 10 ) ;
2010-08-19 14:54:37 +00:00
} else if ( av_strstart ( line , " # " , NULL ) ) {
2025-02-21 13:14:18 +01:00
av_log ( c - > ctx , AV_LOG_VERBOSE , " Skip ('%s') \n " , line ) ;
2010-08-19 14:54:37 +00:00
continue ;
} else if ( line [ 0 ] ) {
if ( is_variant ) {
2013-12-28 00:09:45 +02:00
if ( ! new_variant ( c , & variant_info , line , url ) ) {
2010-08-19 14:54:37 +00:00
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
is_variant = 0 ;
}
if ( is_segment ) {
struct segment * seg ;
2019-07-21 21:55:47 +08:00
ret = ensure_playlist ( c , & pls , url ) ;
if ( ret < 0 )
goto fail ;
2010-08-19 14:54:37 +00:00
seg = av_malloc ( sizeof ( struct segment ) ) ;
if ( ! seg ) {
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2011-01-23 23:42:18 +02:00
if ( has_iv ) {
memcpy ( seg - > iv , iv , sizeof ( iv ) ) ;
} else {
2022-02-08 00:30:59 +01:00
uint64_t seq = pls - > start_seq_no + ( uint64_t ) pls - > n_segments ;
2011-01-23 23:42:18 +02:00
memset ( seg - > iv , 0 , sizeof ( seg - > iv ) ) ;
2021-01-16 11:40:36 +08:00
AV_WB64 ( seg - > iv + 8 , seq ) ;
2011-01-23 23:42:18 +02:00
}
2014-04-11 08:14:40 +02:00
if ( key_type ! = KEY_NONE ) {
ff_make_absolute_url ( tmp_str , sizeof ( tmp_str ) , url , key ) ;
2020-05-28 10:41:26 +08:00
if ( ! tmp_str [ 0 ] ) {
ret = AVERROR_INVALIDDATA ;
av_free ( seg ) ;
goto fail ;
}
2014-04-11 08:14:40 +02:00
seg - > key = av_strdup ( tmp_str ) ;
if ( ! seg - > key ) {
av_free ( seg ) ;
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
} else {
seg - > key = NULL ;
}
ff_make_absolute_url ( tmp_str , sizeof ( tmp_str ) , url , line ) ;
2020-05-28 10:41:26 +08:00
if ( ! tmp_str [ 0 ] ) {
ret = AVERROR_INVALIDDATA ;
if ( seg - > key )
av_free ( seg - > key ) ;
av_free ( seg ) ;
goto fail ;
}
2014-04-11 08:14:40 +02:00
seg - > url = av_strdup ( tmp_str ) ;
if ( ! seg - > url ) {
av_free ( seg - > key ) ;
av_free ( seg ) ;
ret = AVERROR ( ENOMEM ) ;
goto fail ;
}
2025-01-16 01:28:46 +01:00
ret = test_segment ( c - > ctx , pls - > ctx ? pls - > ctx - > iformat : NULL , pls , seg ) ;
if ( ret < 0 ) {
av_free ( seg - > url ) ;
av_free ( seg - > key ) ;
av_free ( seg ) ;
goto fail ;
}
2020-05-29 11:39:05 +08:00
if ( duration < 0.001 * AV_TIME_BASE ) {
av_log ( c - > ctx , AV_LOG_WARNING , " Cannot get correct #EXTINF value of segment %s, "
" set to default value to 1ms. \n " , seg - > url ) ;
duration = 0.001 * AV_TIME_BASE ;
}
seg - > duration = duration ;
seg - > key_type = key_type ;
2013-12-27 13:01:10 +02:00
dynarray_add ( & pls - > segments , & pls - > n_segments , seg ) ;
2010-08-19 14:54:37 +00:00
is_segment = 0 ;
2013-12-28 09:42:46 +02:00
seg - > size = seg_size ;
if ( seg_size > = 0 ) {
seg - > url_offset = seg_offset ;
seg_offset + = seg_size ;
seg_size = - 1 ;
} else {
seg - > url_offset = 0 ;
seg_offset = 0 ;
}
2015-10-15 14:23:00 +03:00
seg - > init_section = cur_init_section ;
2010-08-19 14:54:37 +00:00
}
}
}
2018-04-23 17:30:21 -07:00
if ( prev_segments ) {
if ( pls - > start_seq_no > prev_start_seq_no & & c - > first_timestamp ! = AV_NOPTS_VALUE ) {
int64_t prev_timestamp = c - > first_timestamp ;
2021-01-16 11:40:36 +08:00
int i ;
int64_t diff = pls - > start_seq_no - prev_start_seq_no ;
2018-04-23 17:30:21 -07:00
for ( i = 0 ; i < prev_n_segments & & i < diff ; i + + ) {
c - > first_timestamp + = prev_segments [ i ] - > duration ;
}
2021-01-16 11:40:36 +08:00
av_log ( c - > ctx , AV_LOG_DEBUG , " Media sequence change (% " PRId64 " -> % " PRId64 " ) "
2018-04-23 17:30:21 -07:00
" reflected in first_timestamp: % " PRId64 " -> % " PRId64 " \n " ,
prev_start_seq_no , pls - > start_seq_no ,
prev_timestamp , c - > first_timestamp ) ;
} else if ( pls - > start_seq_no < prev_start_seq_no ) {
2021-01-16 11:40:36 +08:00
av_log ( c - > ctx , AV_LOG_WARNING , " Media sequence changed unexpectedly: % " PRId64 " -> % " PRId64 " \n " ,
2018-04-23 17:30:21 -07:00
prev_start_seq_no , pls - > start_seq_no ) ;
}
free_segment_dynarray ( prev_segments , prev_n_segments ) ;
2018-12-24 10:19:55 +01:00
av_freep ( & prev_segments ) ;
2018-04-23 17:30:21 -07:00
}
2013-12-27 13:01:10 +02:00
if ( pls )
2014-10-24 12:40:08 +02:00
pls - > last_load_time = av_gettime_relative ( ) ;
2010-08-19 14:54:37 +00:00
fail :
2013-11-21 11:31:53 +02:00
av_free ( new_url ) ;
2010-08-19 14:54:37 +00:00
if ( close_in )
2016-01-16 17:53:43 +01:00
ff_format_io_close ( c - > ctx , & in ) ;
2018-01-24 08:02:25 +01:00
c - > ctx - > ctx_flags = c - > ctx - > ctx_flags & ~ ( unsigned ) AVFMTCTX_UNSEEKABLE ;
if ( ! c - > n_variants | | ! c - > variants [ 0 ] - > n_playlists | |
! ( c - > variants [ 0 ] - > playlists [ 0 ] - > finished | |
c - > variants [ 0 ] - > playlists [ 0 ] - > type = = PLS_TYPE_EVENT ) )
c - > ctx - > ctx_flags | = AVFMTCTX_UNSEEKABLE ;
2010-08-19 14:54:37 +00:00
return ret ;
}
2015-10-15 14:23:00 +03:00
static struct segment * current_segment ( struct playlist * pls )
{
2021-09-22 00:12:00 +05:30
int64_t n = pls - > cur_seq_no - pls - > start_seq_no ;
if ( n > = pls - > n_segments )
return NULL ;
return pls - > segments [ n ] ;
2015-10-15 14:23:00 +03:00
}
2017-12-12 15:25:46 -08:00
static struct segment * next_segment ( struct playlist * pls )
{
2021-01-16 11:40:36 +08:00
int64_t n = pls - > cur_seq_no - pls - > start_seq_no + 1 ;
2017-12-12 15:25:46 -08:00
if ( n > = pls - > n_segments )
return NULL ;
return pls - > segments [ n ] ;
}
2015-10-15 14:23:00 +03:00
static int read_from_url ( struct playlist * pls , struct segment * seg ,
2018-04-17 13:16:33 +08:00
uint8_t * buf , int buf_size )
2014-04-05 12:16:27 +03:00
{
int ret ;
/* limit read if the segment was only a part of a file */
if ( seg - > size > = 0 )
buf_size = FFMIN ( buf_size , seg - > size - pls - > cur_seg_offset ) ;
2018-04-17 13:16:33 +08:00
ret = avio_read ( pls - > input , buf , buf_size ) ;
2014-04-05 12:16:27 +03:00
if ( ret > 0 )
pls - > cur_seg_offset + = ret ;
return ret ;
}
2014-04-05 14:47:26 +03:00
/* Parse the raw ID3 data and pass contents to caller */
static void parse_id3 ( AVFormatContext * s , AVIOContext * pb ,
2021-09-22 00:12:00 +05:30
AVDictionary * * metadata , int64_t * dts , HLSAudioSetupInfo * audio_setup_info ,
2014-04-05 14:47:26 +03:00
ID3v2ExtraMetaAPIC * * apic , ID3v2ExtraMeta * * extra_meta )
{
static const char id3_priv_owner_ts [ ] = " com.apple.streaming.transportStreamTimestamp " ;
2021-09-22 00:12:00 +05:30
static const char id3_priv_owner_audio_setup [ ] = " com.apple.streaming.audioDescription " ;
2014-04-05 14:47:26 +03:00
ID3v2ExtraMeta * meta ;
ff_id3v2_read_dict ( pb , metadata , ID3v2_DEFAULT_MAGIC , extra_meta ) ;
for ( meta = * extra_meta ; meta ; meta = meta - > next ) {
if ( ! strcmp ( meta - > tag , " PRIV " ) ) {
2020-05-19 11:43:48 +02:00
ID3v2ExtraMetaPRIV * priv = & meta - > data . priv ;
2021-09-22 00:12:00 +05:30
if ( priv - > datasize = = 8 & & ! av_strncasecmp ( priv - > owner , id3_priv_owner_ts , 44 ) ) {
2014-04-05 14:47:26 +03:00
/* 33-bit MPEG timestamp */
int64_t ts = AV_RB64 ( priv - > data ) ;
av_log ( s , AV_LOG_DEBUG , " HLS ID3 audio timestamp % " PRId64 " \n " , ts ) ;
if ( ( ts & ~ ( ( 1ULL < < 33 ) - 1 ) ) = = 0 )
* dts = ts ;
else
av_log ( s , AV_LOG_ERROR , " Invalid HLS ID3 audio timestamp % " PRId64 " \n " , ts ) ;
2021-09-22 00:12:00 +05:30
} else if ( priv - > datasize > = 8 & & ! av_strncasecmp ( priv - > owner , id3_priv_owner_audio_setup , 36 ) ) {
ff_hls_senc_read_audio_setup_info ( audio_setup_info , priv - > data , priv - > datasize ) ;
2014-04-05 14:47:26 +03:00
}
} else if ( ! strcmp ( meta - > tag , " APIC " ) & & apic )
2020-05-19 11:43:48 +02:00
* apic = & meta - > data . apic ;
2014-04-05 14:47:26 +03:00
}
}
/* Check if the ID3 metadata contents have changed */
static int id3_has_changed_values ( struct playlist * pls , AVDictionary * metadata ,
ID3v2ExtraMetaAPIC * apic )
{
2022-11-26 15:46:37 +01:00
const AVDictionaryEntry * entry = NULL ;
const AVDictionaryEntry * oldentry ;
2014-04-05 14:47:26 +03:00
/* check that no keys have changed values */
2022-11-26 15:46:37 +01:00
while ( ( entry = av_dict_iterate ( metadata , entry ) ) ) {
2014-04-05 14:47:26 +03:00
oldentry = av_dict_get ( pls - > id3_initial , entry - > key , NULL , AV_DICT_MATCH_CASE ) ;
if ( ! oldentry | | strcmp ( oldentry - > value , entry - > value ) ! = 0 )
return 1 ;
}
/* check if apic appeared */
if ( apic & & ( pls - > ctx - > nb_streams ! = 2 | | ! pls - > ctx - > streams [ 1 ] - > attached_pic . data ) )
return 1 ;
if ( apic ) {
int size = pls - > ctx - > streams [ 1 ] - > attached_pic . size ;
2015-07-27 22:53:16 +02:00
if ( size ! = apic - > buf - > size - AV_INPUT_BUFFER_PADDING_SIZE )
2014-04-05 14:47:26 +03:00
return 1 ;
if ( memcmp ( apic - > buf - > data , pls - > ctx - > streams [ 1 ] - > attached_pic . data , size ) ! = 0 )
return 1 ;
}
return 0 ;
}
/* Parse ID3 data and handle the found data */
static void handle_id3 ( AVIOContext * pb , struct playlist * pls )
{
AVDictionary * metadata = NULL ;
ID3v2ExtraMetaAPIC * apic = NULL ;
ID3v2ExtraMeta * extra_meta = NULL ;
int64_t timestamp = AV_NOPTS_VALUE ;
2021-09-22 00:12:00 +05:30
parse_id3 ( pls - > ctx , pb , & metadata , & timestamp , & pls - > audio_setup_info , & apic , & extra_meta ) ;
2014-04-05 14:47:26 +03:00
if ( timestamp ! = AV_NOPTS_VALUE ) {
pls - > id3_mpegts_timestamp = timestamp ;
pls - > id3_offset = 0 ;
}
if ( ! pls - > id3_found ) {
/* initial ID3 tags */
av_assert0 ( ! pls - > id3_deferred_extra ) ;
pls - > id3_found = 1 ;
/* get picture attachment and set text metadata */
if ( pls - > ctx - > nb_streams )
2020-05-19 12:09:48 +02:00
ff_id3v2_parse_apic ( pls - > ctx , extra_meta ) ;
2014-04-05 14:47:26 +03:00
else
/* demuxer not yet opened, defer picture attachment */
pls - > id3_deferred_extra = extra_meta ;
2020-05-19 12:09:48 +02:00
ff_id3v2_parse_priv_dict ( & metadata , extra_meta ) ;
2014-04-05 14:47:26 +03:00
av_dict_copy ( & pls - > ctx - > metadata , metadata , 0 ) ;
pls - > id3_initial = metadata ;
} else {
if ( ! pls - > id3_changed & & id3_has_changed_values ( pls , metadata , apic ) ) {
2019-10-27 10:00:24 +08:00
avpriv_report_missing_feature ( pls - > parent , " Changing ID3 metadata in HLS audio elementary stream " ) ;
2014-04-05 14:47:26 +03:00
pls - > id3_changed = 1 ;
}
av_dict_free ( & metadata ) ;
}
if ( ! pls - > id3_deferred_extra )
ff_id3v2_free_extra_meta ( & extra_meta ) ;
}
static void intercept_id3 ( struct playlist * pls , uint8_t * buf ,
int buf_size , int * len )
{
/* intercept id3 tags, we do not want to pass them to the raw
* demuxer on all segment switches */
int bytes ;
int id3_buf_pos = 0 ;
int fill_buf = 0 ;
2015-10-15 14:23:00 +03:00
struct segment * seg = current_segment ( pls ) ;
2014-04-05 14:47:26 +03:00
/* gather all the id3 tags */
while ( 1 ) {
/* see if we can retrieve enough data for ID3 header */
if ( * len < ID3v2_HEADER_SIZE & & buf_size > = ID3v2_HEADER_SIZE ) {
2018-04-17 13:16:33 +08:00
bytes = read_from_url ( pls , seg , buf + * len , ID3v2_HEADER_SIZE - * len ) ;
2014-04-05 14:47:26 +03:00
if ( bytes > 0 ) {
if ( bytes = = ID3v2_HEADER_SIZE - * len )
/* no EOF yet, so fill the caller buffer again after
* we have stripped the ID3 tags */
fill_buf = 1 ;
* len + = bytes ;
} else if ( * len < = 0 ) {
/* error/EOF */
* len = bytes ;
fill_buf = 0 ;
}
}
if ( * len < ID3v2_HEADER_SIZE )
break ;
if ( ff_id3v2_match ( buf , ID3v2_DEFAULT_MAGIC ) ) {
2014-04-10 23:50:13 +03:00
int64_t maxsize = seg - > size > = 0 ? seg - > size : 1024 * 1024 ;
2014-04-05 14:47:26 +03:00
int taglen = ff_id3v2_tag_len ( buf ) ;
int tag_got_bytes = FFMIN ( taglen , * len ) ;
int remaining = taglen - tag_got_bytes ;
2014-04-10 23:50:13 +03:00
if ( taglen > maxsize ) {
2019-10-27 10:00:24 +08:00
av_log ( pls - > parent , AV_LOG_ERROR , " Too large HLS ID3 tag (%d > % " PRId64 " bytes) \n " ,
2014-04-10 23:50:13 +03:00
taglen , maxsize ) ;
2014-04-05 14:47:26 +03:00
break ;
}
/*
* Copy the id3 tag to our temporary id3 buffer .
* We could read a small id3 tag directly without memcpy , but
* we would still need to copy the large tags , and handling
* both of those cases together with the possibility for multiple
* tags would make the handling a bit complex .
*/
pls - > id3_buf = av_fast_realloc ( pls - > id3_buf , & pls - > id3_buf_size , id3_buf_pos + taglen ) ;
if ( ! pls - > id3_buf )
break ;
memcpy ( pls - > id3_buf + id3_buf_pos , buf , tag_got_bytes ) ;
id3_buf_pos + = tag_got_bytes ;
/* strip the intercepted bytes */
* len - = tag_got_bytes ;
memmove ( buf , buf + tag_got_bytes , * len ) ;
2019-10-27 10:00:24 +08:00
av_log ( pls - > parent , AV_LOG_DEBUG , " Stripped %d HLS ID3 bytes \n " , tag_got_bytes ) ;
2014-04-05 14:47:26 +03:00
if ( remaining > 0 ) {
/* read the rest of the tag in */
2018-04-17 13:16:33 +08:00
if ( read_from_url ( pls , seg , pls - > id3_buf + id3_buf_pos , remaining ) ! = remaining )
2014-04-05 14:47:26 +03:00
break ;
id3_buf_pos + = remaining ;
2019-10-27 10:00:24 +08:00
av_log ( pls - > parent , AV_LOG_DEBUG , " Stripped additional %d HLS ID3 bytes \n " , remaining ) ;
2014-04-05 14:47:26 +03:00
}
} else {
/* no more ID3 tags */
break ;
}
}
/* re-fill buffer for the caller unless EOF */
if ( * len > = 0 & & ( fill_buf | | * len = = 0 ) ) {
2018-04-17 13:16:33 +08:00
bytes = read_from_url ( pls , seg , buf + * len , buf_size - * len ) ;
2014-04-05 14:47:26 +03:00
/* ignore error if we already had some data */
if ( bytes > = 0 )
* len + = bytes ;
else if ( * len = = 0 )
* len = bytes ;
}
if ( pls - > id3_buf ) {
/* Now parse all the ID3 tags */
2021-08-04 16:52:07 +02:00
FFIOContext id3ioctx ;
2023-09-02 15:41:20 +02:00
ffio_init_read_context ( & id3ioctx , pls - > id3_buf , id3_buf_pos ) ;
2021-08-04 16:52:07 +02:00
handle_id3 ( & id3ioctx . pub , pls ) ;
2014-04-05 14:47:26 +03:00
}
if ( pls - > is_id3_timestamped = = - 1 )
pls - > is_id3_timestamped = ( pls - > id3_mpegts_timestamp ! = AV_NOPTS_VALUE ) ;
}
2025-04-30 10:29:56 +08:00
static int read_key ( HLSContext * c , struct playlist * pls , struct segment * seg )
{
AVIOContext * pb = NULL ;
int ret = open_url ( pls - > parent , & pb , seg - > key , & c - > avio_opts , NULL , NULL ) ;
if ( ret < 0 ) {
av_log ( pls - > parent , AV_LOG_ERROR , " Unable to open key file %s, %s \n " ,
seg - > key , av_err2str ( ret ) ) ;
return ret ;
}
ret = avio_read ( pb , pls - > key , sizeof ( pls - > key ) ) ;
ff_format_io_close ( pls - > parent , & pb ) ;
if ( ret ! = sizeof ( pls - > key ) ) {
if ( ret < 0 ) {
av_log ( pls - > parent , AV_LOG_ERROR , " Unable to read key file %s, %s \n " ,
seg - > key , av_err2str ( ret ) ) ;
} else {
av_log ( pls - > parent , AV_LOG_ERROR , " Unable to read key file %s, read bytes %d != %zu \n " ,
seg - > key , ret , sizeof ( pls - > key ) ) ;
ret = AVERROR_INVALIDDATA ;
}
return ret ;
}
av_strlcpy ( pls - > key_url , seg - > key , sizeof ( pls - > key_url ) ) ;
return 0 ;
}
2017-12-12 15:21:29 -08:00
static int open_input ( HLSContext * c , struct playlist * pls , struct segment * seg , AVIOContext * * in )
2011-01-23 23:42:18 +02:00
{
2012-10-02 22:22:44 +01:00
AVDictionary * opts = NULL ;
int ret ;
2016-07-26 15:18:40 +03:00
int is_http = 0 ;
2013-01-22 21:09:57 -05:00
2017-10-04 14:52:52 -07:00
if ( c - > http_persistent )
av_dict_set ( & opts , " multiple_requests " , " 1 " , 0 ) ;
2013-12-28 09:42:46 +02:00
if ( seg - > size > = 0 ) {
/* try to restrict the HTTP request to the part we want
* ( if this is in fact a HTTP request ) */
2014-07-29 21:10:39 +02:00
av_dict_set_int ( & opts , " offset " , seg - > url_offset , 0 ) ;
av_dict_set_int ( & opts , " end_offset " , seg - > url_offset + seg - > size , 0 ) ;
2013-12-28 09:42:46 +02:00
}
2013-12-28 09:41:55 +02:00
av_log ( pls - > parent , AV_LOG_VERBOSE , " HLS request for url '%s', offset % " PRId64 " , playlist %d \n " ,
seg - > url , seg - > url_offset , pls - > index ) ;
2021-09-22 00:12:00 +05:30
if ( seg - > key_type = = KEY_AES_128 | | seg - > key_type = = KEY_SAMPLE_AES ) {
2013-12-27 13:01:10 +02:00
if ( strcmp ( seg - > key , pls - > key_url ) ) {
2025-04-30 10:29:56 +08:00
ret = read_key ( c , pls , seg ) ;
if ( ret < 0 )
goto cleanup ;
2011-01-23 23:42:18 +02:00
}
2021-09-22 00:12:00 +05:30
}
if ( seg - > key_type = = KEY_AES_128 ) {
char iv [ 33 ] , key [ 33 ] , url [ MAX_URL_SIZE ] ;
2011-01-23 23:42:18 +02:00
ff_data_to_hex ( iv , seg - > iv , sizeof ( seg - > iv ) , 0 ) ;
2013-12-27 13:01:10 +02:00
ff_data_to_hex ( key , pls - > key , sizeof ( pls - > key ) , 0 ) ;
2011-01-23 23:42:18 +02:00
if ( strstr ( seg - > url , " :// " ) )
snprintf ( url , sizeof ( url ) , " crypto+%s " , seg - > url ) ;
else
snprintf ( url , sizeof ( url ) , " crypto:%s " , seg - > url ) ;
2015-07-23 21:11:58 +02:00
2018-04-17 14:40:06 +08:00
av_dict_set ( & opts , " key " , key , 0 ) ;
av_dict_set ( & opts , " iv " , iv , 0 ) ;
2014-01-27 11:05:27 +01:00
2020-09-07 20:49:02 +02:00
ret = open_url ( pls - > parent , in , url , & c - > avio_opts , opts , & is_http ) ;
2016-02-16 16:26:49 +00:00
if ( ret < 0 ) {
2012-10-02 22:22:44 +01:00
goto cleanup ;
2011-01-23 23:42:18 +02:00
}
2012-10-02 22:22:44 +01:00
ret = 0 ;
2021-09-22 00:12:00 +05:30
} else {
ret = open_url ( pls - > parent , in , seg - > url , & c - > avio_opts , opts , & is_http ) ;
2011-01-23 23:42:18 +02:00
}
2012-10-02 22:22:44 +01:00
2013-12-28 09:42:46 +02:00
/* Seek to the requested position. If this was a HTTP request, the offset
* should already be where want it to , but this allows e . g . local testing
2016-07-26 15:18:40 +03:00
* without a HTTP server .
*
* This is not done for HTTP at all as avio_seek ( ) does internal bookkeeping
* of file offset which is out - of - sync with the actual offset when " offset "
* AVOption is used with http protocol , causing the seek to not be a no - op
* as would be expected . Wrong offset received from the server will not be
* noticed without the call , though .
*/
2020-07-22 17:15:28 +08:00
if ( ret = = 0 & & ! is_http & & seg - > url_offset ) {
2017-12-12 15:21:29 -08:00
int64_t seekret = avio_seek ( * in , seg - > url_offset , SEEK_SET ) ;
2013-12-28 09:42:46 +02:00
if ( seekret < 0 ) {
av_log ( pls - > parent , AV_LOG_ERROR , " Unable to seek to offset % " PRId64 " of HLS segment '%s' \n " , seg - > url_offset , seg - > url ) ;
ret = seekret ;
2017-12-12 15:21:29 -08:00
ff_format_io_close ( pls - > parent , in ) ;
2013-12-28 09:42:46 +02:00
}
}
2012-10-02 22:22:44 +01:00
cleanup :
av_dict_free ( & opts ) ;
2013-12-28 09:42:46 +02:00
pls - > cur_seg_offset = 0 ;
2012-10-02 22:22:44 +01:00
return ret ;
2011-01-23 23:42:18 +02:00
}
2015-10-15 14:23:00 +03:00
static int update_init_section ( struct playlist * pls , struct segment * seg )
{
static const int max_init_section_size = 1024 * 1024 ;
HLSContext * c = pls - > parent - > priv_data ;
int64_t sec_size ;
int64_t urlsize ;
int ret ;
if ( seg - > init_section = = pls - > cur_init_section )
return 0 ;
pls - > cur_init_section = NULL ;
if ( ! seg - > init_section )
return 0 ;
2017-12-12 15:21:29 -08:00
ret = open_input ( c , pls , seg - > init_section , & pls - > input ) ;
2015-10-15 14:23:00 +03:00
if ( ret < 0 ) {
av_log ( pls - > parent , AV_LOG_WARNING ,
" Failed to open an initialization section in playlist %d \n " ,
pls - > index ) ;
return ret ;
}
if ( seg - > init_section - > size > = 0 )
sec_size = seg - > init_section - > size ;
2016-02-16 16:26:49 +00:00
else if ( ( urlsize = avio_size ( pls - > input ) ) > = 0 )
2015-10-15 14:23:00 +03:00
sec_size = urlsize ;
else
sec_size = max_init_section_size ;
av_log ( pls - > parent , AV_LOG_DEBUG ,
" Downloading an initialization section of size % " PRId64 " \n " ,
sec_size ) ;
sec_size = FFMIN ( sec_size , max_init_section_size ) ;
av_fast_malloc ( & pls - > init_sec_buf , & pls - > init_sec_buf_size , sec_size ) ;
ret = read_from_url ( pls , seg - > init_section , pls - > init_sec_buf ,
2018-04-17 13:16:33 +08:00
pls - > init_sec_buf_size ) ;
2016-02-16 16:26:49 +00:00
ff_format_io_close ( pls - > parent , & pls - > input ) ;
2015-10-15 14:23:00 +03:00
if ( ret < 0 )
return ret ;
pls - > cur_init_section = seg - > init_section ;
pls - > init_sec_data_len = ret ;
pls - > init_sec_buf_read_offset = 0 ;
/* spec says audio elementary streams do not have media initialization
* sections , so there should be no ID3 timestamps */
pls - > is_id3_timestamped = 0 ;
return 0 ;
}
2013-12-30 11:27:36 +02:00
static int64_t default_reload_interval ( struct playlist * pls )
{
return pls - > n_segments > 0 ?
pls - > segments [ pls - > n_segments - 1 ] - > duration :
pls - > target_duration ;
}
2017-11-19 19:30:02 +02:00
static int playlist_needed ( struct playlist * pls )
{
2017-11-19 20:11:49 +02:00
AVFormatContext * s = pls - > parent ;
int i , j ;
int stream_needed = 0 ;
int first_st ;
2017-11-19 19:30:02 +02:00
/* If there is no context or streams yet, the playlist is needed */
2025-02-21 13:14:18 +01:00
if ( ( ! pls - > ctx | | ! pls - > n_main_streams ) & & ! pls - > is_subtitle )
2017-11-19 19:30:02 +02:00
return 1 ;
/* check if any of the streams in the playlist are needed */
for ( i = 0 ; i < pls - > n_main_streams ; i + + ) {
if ( pls - > main_streams [ i ] - > discard < AVDISCARD_ALL ) {
2017-11-19 20:11:49 +02:00
stream_needed = 1 ;
break ;
}
}
/* If all streams in the playlist were discarded, the playlist is not
* needed ( regardless of whether whole programs are discarded or not ) . */
if ( ! stream_needed )
return 0 ;
/* Otherwise, check if all the programs (variants) this playlist is in are
* discarded . Since all streams in the playlist are part of the same programs
* we can just check the programs of the first stream . */
first_st = pls - > main_streams [ 0 ] - > index ;
for ( i = 0 ; i < s - > nb_programs ; i + + ) {
AVProgram * program = s - > programs [ i ] ;
if ( program - > discard < AVDISCARD_ALL ) {
for ( j = 0 ; j < program - > nb_stream_indexes ; j + + ) {
if ( program - > stream_index [ j ] = = first_st ) {
/* playlist is in an undiscarded program */
return 1 ;
}
}
2017-11-19 19:30:02 +02:00
}
}
2017-11-19 20:11:49 +02:00
/* some streams were not discarded but all the programs were */
2017-11-19 19:30:02 +02:00
return 0 ;
}
2025-02-21 13:14:18 +01:00
static int reload_playlist ( struct playlist * v , HLSContext * c )
2011-03-21 00:21:56 +02:00
{
2025-02-21 13:14:18 +01:00
int ret = 0 ;
2017-08-26 01:26:58 +02:00
int reload_count = 0 ;
2011-03-21 00:21:56 +02:00
2025-02-21 13:14:18 +01:00
v - > needed = playlist_needed ( v ) ;
2013-12-28 09:42:32 +02:00
if ( ! v - > needed )
return AVERROR_EOF ;
2017-10-04 14:52:52 -07:00
if ( ! v - > input | | ( c - > http_persistent & & v - > input_read_done ) ) {
2013-12-30 11:40:20 +02:00
int64_t reload_interval ;
/* Check that the playlist is still needed before opening a new
* segment . */
2017-11-19 19:30:02 +02:00
v - > needed = playlist_needed ( v ) ;
2013-12-30 11:40:20 +02:00
if ( ! v - > needed ) {
2019-06-14 09:22:04 +08:00
av_log ( v - > parent , AV_LOG_INFO , " No longer receiving playlist %d ('%s') \n " ,
v - > index , v - > url ) ;
2013-12-30 11:40:20 +02:00
return AVERROR_EOF ;
}
2011-12-26 21:25:52 +02:00
/* If this is a live stream and the reload interval has elapsed since
2013-12-27 13:01:10 +02:00
* the last playlist reload , reload the playlists now . */
2013-12-30 11:40:20 +02:00
reload_interval = default_reload_interval ( v ) ;
2011-12-26 21:25:52 +02:00
2011-12-27 13:30:55 +02:00
reload :
2017-08-26 01:26:58 +02:00
reload_count + + ;
if ( reload_count > c - > max_reload )
return AVERROR_EOF ;
2011-03-21 00:21:56 +02:00
if ( ! v - > finished & &
2014-10-22 12:40:46 +03:00
av_gettime_relative ( ) - v - > last_load_time > = reload_interval ) {
2013-12-28 09:41:55 +02:00
if ( ( ret = parse_playlist ( c , v - > url , v , NULL ) ) < 0 ) {
2018-01-24 08:04:38 +01:00
if ( ret ! = AVERROR_EXIT )
av_log ( v - > parent , AV_LOG_WARNING , " Failed to reload playlist %d \n " ,
v - > index ) ;
2011-03-21 00:21:56 +02:00
return ret ;
2013-12-28 09:41:55 +02:00
}
2011-12-27 13:30:55 +02:00
/* If we need to reload the playlist again below (if
* there ' s still no more segments ) , switch to a reload
* interval of half the target duration . */
2013-07-29 10:26:02 +03:00
reload_interval = v - > target_duration / 2 ;
2011-12-27 13:30:55 +02:00
}
2011-03-21 00:21:56 +02:00
if ( v - > cur_seq_no < v - > start_seq_no ) {
2019-06-01 18:45:35 +08:00
av_log ( v - > parent , AV_LOG_WARNING ,
2021-01-16 11:40:36 +08:00
" skipping % " PRId64 " segments ahead, expired from playlists \n " ,
2011-03-21 00:21:56 +02:00
v - > start_seq_no - v - > cur_seq_no ) ;
v - > cur_seq_no = v - > start_seq_no ;
}
2019-11-18 17:37:00 +08:00
if ( v - > cur_seq_no > v - > last_seq_no ) {
v - > last_seq_no = v - > cur_seq_no ;
v - > m3u8_hold_counters = 0 ;
} else if ( v - > last_seq_no = = v - > cur_seq_no ) {
v - > m3u8_hold_counters + + ;
if ( v - > m3u8_hold_counters > = c - > m3u8_hold_counters ) {
return AVERROR_EOF ;
}
} else {
2022-10-05 10:43:55 -04:00
av_log ( v - > parent , AV_LOG_WARNING , " The m3u8 list sequence may have been wrapped. \n " ) ;
2019-11-18 17:37:00 +08:00
}
2011-03-21 00:21:56 +02:00
if ( v - > cur_seq_no > = v - > start_seq_no + v - > n_segments ) {
2025-02-21 13:14:18 +01:00
if ( v - > finished | | v - > is_subtitle )
2011-03-21 00:21:56 +02:00
return AVERROR_EOF ;
2014-10-22 12:40:46 +03:00
while ( av_gettime_relative ( ) - v - > last_load_time < reload_interval ) {
2011-11-06 22:34:24 +02:00
if ( ff_check_interrupt ( c - > interrupt_callback ) )
2011-03-21 00:21:56 +02:00
return AVERROR_EXIT ;
2012-06-21 20:31:44 +01:00
av_usleep ( 100 * 1000 ) ;
2011-03-21 00:21:56 +02:00
}
/* Enough time has elapsed since the last reload */
goto reload ;
}
2025-02-21 13:14:18 +01:00
}
return ret ;
}
2015-10-15 14:23:00 +03:00
2025-02-21 13:14:18 +01:00
static int read_data_continuous ( void * opaque , uint8_t * buf , int buf_size )
{
struct playlist * v = opaque ;
HLSContext * c = v - > parent - > priv_data ;
int ret ;
int just_opened = 0 ;
int segment_retries = 0 ;
struct segment * seg ;
if ( c - > http_persistent & & v - > input_read_done ) {
ret = reload_playlist ( v , c ) ;
if ( ret < 0 )
return ret ;
}
v - > input_read_done = 0 ;
restart :
ret = reload_playlist ( v , c ) ;
if ( ret < 0 )
return ret ;
seg = current_segment ( v ) ;
if ( ! v - > input | | ( c - > http_persistent & & v - > input_read_done ) ) {
2015-10-15 14:23:00 +03:00
/* load/update Media Initialization Section, if any */
ret = update_init_section ( v , seg ) ;
if ( ret )
return ret ;
2017-12-25 20:37:55 -08:00
if ( c - > http_multiple = = 1 & & v - > input_next_requested ) {
2017-12-12 15:25:46 -08:00
FFSWAP ( AVIOContext * , v - > input , v - > input_next ) ;
2019-10-04 23:04:12 +01:00
v - > cur_seg_offset = 0 ;
2017-12-12 15:25:46 -08:00
v - > input_next_requested = 0 ;
ret = 0 ;
} else {
ret = open_input ( c , v , seg , & v - > input ) ;
}
2013-12-28 09:41:55 +02:00
if ( ret < 0 ) {
2015-06-26 06:20:34 +02:00
if ( ff_check_interrupt ( c - > interrupt_callback ) )
return AVERROR_EXIT ;
2021-01-16 11:40:36 +08:00
av_log ( v - > parent , AV_LOG_WARNING , " Failed to open segment % " PRId64 " of playlist %d \n " ,
2017-12-12 15:25:46 -08:00
v - > cur_seq_no ,
2013-12-28 09:41:55 +02:00
v - > index ) ;
2022-10-20 20:11:38 +08:00
if ( segment_retries > = c - > seg_max_retry ) {
av_log ( v - > parent , AV_LOG_WARNING , " Segment % " PRId64 " of playlist %d failed too many times, skipping \n " ,
v - > cur_seq_no ,
v - > index ) ;
v - > cur_seq_no + + ;
segment_retries = 0 ;
} else {
segment_retries + + ;
}
2025-02-21 13:14:18 +01:00
goto restart ;
2013-12-28 09:41:55 +02:00
}
2022-10-20 20:11:38 +08:00
segment_retries = 0 ;
2014-04-05 14:47:26 +03:00
just_opened = 1 ;
2011-03-21 00:21:56 +02:00
}
2013-12-28 09:42:46 +02:00
2017-12-25 20:37:55 -08:00
if ( c - > http_multiple = = - 1 ) {
uint8_t * http_version_opt = NULL ;
2017-12-30 20:42:13 +08:00
int r = av_opt_get ( v - > input , " http_version " , AV_OPT_SEARCH_CHILDREN , & http_version_opt ) ;
if ( r > = 0 ) {
2019-06-04 15:58:35 +08:00
c - > http_multiple = ( ! strncmp ( ( const char * ) http_version_opt , " 1.1 " , 3 ) | | ! strncmp ( ( const char * ) http_version_opt , " 2.0 " , 3 ) ) ;
2017-12-30 20:42:13 +08:00
av_freep ( & http_version_opt ) ;
}
2017-12-25 20:37:55 -08:00
}
2017-12-12 15:25:46 -08:00
seg = next_segment ( v ) ;
2017-12-25 20:37:55 -08:00
if ( c - > http_multiple = = 1 & & ! v - > input_next_requested & &
2017-12-29 15:30:55 -08:00
seg & & seg - > key_type = = KEY_NONE & & av_strstart ( seg - > url , " http " , NULL ) ) {
2017-12-12 15:25:46 -08:00
ret = open_input ( c , v , seg , & v - > input_next ) ;
if ( ret < 0 ) {
if ( ff_check_interrupt ( c - > interrupt_callback ) )
return AVERROR_EXIT ;
2021-01-16 11:40:36 +08:00
av_log ( v - > parent , AV_LOG_WARNING , " Failed to open segment % " PRId64 " of playlist %d \n " ,
2017-12-12 15:25:46 -08:00
v - > cur_seq_no + 1 ,
v - > index ) ;
} else {
v - > input_next_requested = 1 ;
}
}
2015-10-15 14:23:00 +03:00
if ( v - > init_sec_buf_read_offset < v - > init_sec_data_len ) {
/* Push init section out first before first actual segment */
int copy_size = FFMIN ( v - > init_sec_data_len - v - > init_sec_buf_read_offset , buf_size ) ;
memcpy ( buf , v - > init_sec_buf , copy_size ) ;
v - > init_sec_buf_read_offset + = copy_size ;
return copy_size ;
}
2017-12-24 12:31:27 -08:00
seg = current_segment ( v ) ;
2018-04-17 13:16:33 +08:00
ret = read_from_url ( v , seg , buf , buf_size ) ;
2013-12-28 09:42:46 +02:00
if ( ret > 0 ) {
2014-04-05 14:47:26 +03:00
if ( just_opened & & v - > is_id3_timestamped ! = 0 ) {
/* Intercept ID3 tags here, elementary audio streams are required
* to convey timestamps using them in the beginning of each segment . */
intercept_id3 ( v , buf , buf_size , & ret ) ;
}
2011-03-21 00:21:56 +02:00
return ret ;
2013-12-28 09:42:46 +02:00
}
2017-12-29 15:30:55 -08:00
if ( c - > http_persistent & &
seg - > key_type = = KEY_NONE & & av_strstart ( seg - > url , " http " , NULL ) ) {
2017-10-04 14:52:52 -07:00
v - > input_read_done = 1 ;
} else {
ff_format_io_close ( v - > parent , & v - > input ) ;
}
2011-03-21 00:21:56 +02:00
v - > cur_seq_no + + ;
c - > cur_seq_no = v - > cur_seq_no ;
goto restart ;
}
2025-02-21 13:14:18 +01:00
static int read_data_subtitle_segment ( void * opaque , uint8_t * buf , int buf_size )
{
struct playlist * v = opaque ;
HLSContext * c = v - > parent - > priv_data ;
int ret ;
struct segment * seg ;
if ( ! v - > needed | | v - > cur_seq_no - v - > start_seq_no > = v - > n_segments ) {
return AVERROR_EOF ;
} else {
seg = current_segment ( v ) ;
}
if ( ! v - > input ) {
ret = open_input ( c , v , seg , & v - > input ) ;
if ( ret < 0 ) {
if ( ff_check_interrupt ( c - > interrupt_callback ) )
return AVERROR_EXIT ;
av_log ( v - > parent , AV_LOG_WARNING , " Failed to open segment of playlist %d \n " ,
v - > index ) ;
return ret ;
}
}
return read_from_url ( v , seg , buf , buf_size ) ;
}
static int nested_io_open ( AVFormatContext * s , AVIOContext * * pb , const char * url ,
int flags , AVDictionary * * opts )
{
av_log ( s , AV_LOG_ERROR ,
" A HLS playlist item '%s' referred to an external file '%s'. "
" Opening this file was forbidden for security reasons \n " ,
s - > url , url ) ;
return AVERROR ( EPERM ) ;
}
static int init_subtitle_context ( struct playlist * pls )
{
HLSContext * c = pls - > parent - > priv_data ;
const AVInputFormat * in_fmt ;
AVDictionary * opts = NULL ;
int ret ;
if ( ! ( pls - > ctx = avformat_alloc_context ( ) ) )
return AVERROR ( ENOMEM ) ;
pls - > read_buffer = av_malloc ( INITIAL_BUFFER_SIZE ) ;
if ( ! pls - > read_buffer ) {
avformat_free_context ( pls - > ctx ) ;
pls - > ctx = NULL ;
return AVERROR ( ENOMEM ) ;
}
ffio_init_context ( & pls - > pb , pls - > read_buffer , INITIAL_BUFFER_SIZE , 0 , pls ,
read_data_subtitle_segment , NULL , NULL ) ;
pls - > pb . pub . seekable = 0 ;
pls - > ctx - > pb = & pls - > pb . pub ;
pls - > ctx - > io_open = nested_io_open ;
ret = ff_copy_whiteblacklists ( pls - > ctx , pls - > parent ) ;
if ( ret < 0 )
return ret ;
in_fmt = av_find_input_format ( " webvtt " ) ;
av_dict_copy ( & opts , c - > seg_format_opts , 0 ) ;
ret = avformat_open_input ( & pls - > ctx , current_segment ( pls ) - > url , in_fmt , & opts ) ;
av_dict_free ( & opts ) ;
return ret ;
}
static int read_subtitle_packet ( struct playlist * v , AVPacket * pkt )
{
HLSContext * c = v - > parent - > priv_data ;
int ret ;
restart :
ret = reload_playlist ( v , c ) ;
if ( ret < 0 )
return ret ;
if ( v - > input & & ! v - > ctx )
ff_format_io_close ( v - > parent , & v - > input ) ;
if ( ! v - > input & & ! v - > ctx ) {
ret = init_subtitle_context ( v ) ;
if ( ret < 0 )
return ret ;
}
ret = av_read_frame ( v - > ctx , v - > pkt ) ;
if ( ! ret ) {
return ret ;
}
ff_format_io_close ( v - > parent , & v - > input ) ;
v - > cur_seq_no + + ;
c - > cur_seq_no = v - > cur_seq_no ;
avformat_close_input ( & v - > ctx ) ;
goto restart ;
}
2013-12-28 00:09:45 +02:00
static void add_renditions_to_variant ( HLSContext * c , struct variant * var ,
enum AVMediaType type , const char * group_id )
{
int i ;
for ( i = 0 ; i < c - > n_renditions ; i + + ) {
struct rendition * rend = c - > renditions [ i ] ;
if ( rend - > type = = type & & ! strcmp ( rend - > group_id , group_id ) ) {
if ( rend - > playlist )
/* rendition is an external playlist
* = > add the playlist to the variant */
dynarray_add ( & var - > playlists , & var - > n_playlists , rend - > playlist ) ;
else
/* rendition is part of the variant main Media Playlist
* = > add the rendition to the main Media Playlist */
dynarray_add ( & var - > playlists [ 0 ] - > renditions ,
& var - > playlists [ 0 ] - > n_renditions ,
rend ) ;
}
}
}
static void add_metadata_from_renditions ( AVFormatContext * s , struct playlist * pls ,
enum AVMediaType type )
{
int rend_idx = 0 ;
int i ;
2016-07-27 23:29:16 +03:00
for ( i = 0 ; i < pls - > n_main_streams ; i + + ) {
AVStream * st = pls - > main_streams [ i ] ;
2013-12-28 00:09:45 +02:00
2016-04-10 20:58:15 +01:00
if ( st - > codecpar - > codec_type ! = type )
2013-12-28 00:09:45 +02:00
continue ;
for ( ; rend_idx < pls - > n_renditions ; rend_idx + + ) {
struct rendition * rend = pls - > renditions [ rend_idx ] ;
if ( rend - > type ! = type )
continue ;
if ( rend - > language [ 0 ] )
av_dict_set ( & st - > metadata , " language " , rend - > language , 0 ) ;
if ( rend - > name [ 0 ] )
av_dict_set ( & st - > metadata , " comment " , rend - > name , 0 ) ;
st - > disposition | = rend - > disposition ;
}
if ( rend_idx > = pls - > n_renditions )
break ;
}
}
2013-12-30 11:13:56 +02:00
/* if timestamp was in valid range: returns 1 and sets seq_no
* if not : returns 0 and sets seq_no to closest segment */
static int find_timestamp_in_playlist ( HLSContext * c , struct playlist * pls ,
2022-01-28 21:23:20 +01:00
int64_t timestamp , int64_t * seq_no ,
int64_t * seg_start_ts )
2013-12-30 11:13:56 +02:00
{
int i ;
int64_t pos = c - > first_timestamp = = AV_NOPTS_VALUE ?
0 : c - > first_timestamp ;
if ( timestamp < pos ) {
* seq_no = pls - > start_seq_no ;
return 0 ;
}
for ( i = 0 ; i < pls - > n_segments ; i + + ) {
int64_t diff = pos + pls - > segments [ i ] - > duration - timestamp ;
if ( diff > 0 ) {
* seq_no = pls - > start_seq_no + i ;
2022-01-28 21:23:20 +01:00
if ( seg_start_ts ) {
* seg_start_ts = pos ;
}
2013-12-30 11:13:56 +02:00
return 1 ;
}
pos + = pls - > segments [ i ] - > duration ;
}
* seq_no = pls - > start_seq_no + pls - > n_segments - 1 ;
return 0 ;
}
2021-01-16 11:40:36 +08:00
static int64_t select_cur_seq_no ( HLSContext * c , struct playlist * pls )
2013-12-30 11:27:36 +02:00
{
2021-01-16 11:40:36 +08:00
int64_t seq_no ;
2013-12-30 11:27:36 +02:00
if ( ! pls - > finished & & ! c - > first_packet & &
2014-10-24 12:40:08 +02:00
av_gettime_relative ( ) - pls - > last_load_time > = default_reload_interval ( pls ) )
2013-12-30 11:27:36 +02:00
/* reload the playlist since it was suspended */
parse_playlist ( c , pls - > url , pls , NULL ) ;
/* If playback is already in progress (we are just selecting a new
* playlist ) and this is a complete file , find the matching segment
* by counting durations . */
if ( pls - > finished & & c - > cur_timestamp ! = AV_NOPTS_VALUE ) {
2022-01-28 21:23:20 +01:00
find_timestamp_in_playlist ( c , pls , c - > cur_timestamp , & seq_no , NULL ) ;
2013-12-30 11:27:36 +02:00
return seq_no ;
}
if ( ! pls - > finished ) {
if ( ! c - > first_packet & & /* we are doing a segment selection during playback */
c - > cur_seq_no > = pls - > start_seq_no & &
c - > cur_seq_no < pls - > start_seq_no + pls - > n_segments )
/* While spec 3.4.3 says that we cannot assume anything about the
* content at the same sequence number on different playlists ,
* in practice this seems to work and doing it otherwise would
* require us to download a segment to inspect its timestamps . */
return c - > cur_seq_no ;
2015-03-28 18:27:35 -06:00
/* If this is a live stream, start live_start_index segments from the
* start or end */
if ( c - > live_start_index < 0 )
2022-06-23 00:55:38 +08:00
seq_no = pls - > start_seq_no + FFMAX ( pls - > n_segments +
c - > live_start_index , 0 ) ;
2015-03-28 18:27:35 -06:00
else
2022-06-23 00:55:38 +08:00
seq_no = pls - > start_seq_no + FFMIN ( c - > live_start_index ,
pls - > n_segments - 1 ) ;
/* If #EXT-X-START in playlist, need to recalculate */
if ( pls - > time_offset_flag & & c - > prefer_x_start ) {
int64_t start_timestamp ;
int64_t playlist_duration = 0 ;
int64_t cur_timestamp = c - > cur_timestamp = = AV_NOPTS_VALUE ? 0 :
c - > cur_timestamp ;
for ( int i = 0 ; i < pls - > n_segments ; i + + )
playlist_duration + = pls - > segments [ i ] - > duration ;
/* If the absolute value of TIME-OFFSET exceeds
* the duration of the playlist , it indicates either the end of the
* playlist ( if positive ) or the beginning of the playlist ( if
* negative ) . */
if ( pls - > start_time_offset > = 0 & &
pls - > start_time_offset > playlist_duration )
start_timestamp = cur_timestamp + playlist_duration ;
else if ( pls - > start_time_offset > = 0 & &
pls - > start_time_offset < = playlist_duration )
start_timestamp = cur_timestamp + pls - > start_time_offset ;
else if ( pls - > start_time_offset < 0 & &
pls - > start_time_offset < - playlist_duration )
start_timestamp = cur_timestamp ;
else if ( pls - > start_time_offset < 0 & &
pls - > start_time_offset > - playlist_duration )
start_timestamp = cur_timestamp + playlist_duration +
pls - > start_time_offset ;
else
start_timestamp = cur_timestamp ;
find_timestamp_in_playlist ( c , pls , start_timestamp , & seq_no , NULL ) ;
}
return seq_no ;
2013-12-30 11:27:36 +02:00
}
/* Otherwise just start on the first segment. */
return pls - > start_seq_no ;
}
2016-07-28 00:00:37 +03:00
static void add_stream_to_programs ( AVFormatContext * s , struct playlist * pls , AVStream * stream )
{
HLSContext * c = s - > priv_data ;
int i , j ;
int bandwidth = - 1 ;
for ( i = 0 ; i < c - > n_variants ; i + + ) {
struct variant * v = c - > variants [ i ] ;
for ( j = 0 ; j < v - > n_playlists ; j + + ) {
if ( v - > playlists [ j ] ! = pls )
continue ;
av_program_add_stream_index ( s , i , stream - > index ) ;
if ( bandwidth < 0 )
bandwidth = v - > bandwidth ;
else if ( bandwidth ! = v - > bandwidth )
bandwidth = - 1 ; /* stream in multiple variants with different bandwidths */
}
}
if ( bandwidth > = 0 )
av_dict_set_int ( & stream - > metadata , " variant_bitrate " , bandwidth , 0 ) ;
}
2016-11-06 23:23:20 +02:00
static int set_stream_info_from_input_stream ( AVStream * st , struct playlist * pls , AVStream * ist )
2016-11-05 18:04:00 +02:00
{
2016-11-06 23:23:20 +02:00
int err ;
err = avcodec_parameters_copy ( st - > codecpar , ist - > codecpar ) ;
if ( err < 0 )
return err ;
2016-11-05 18:04:00 +02:00
if ( pls - > is_id3_timestamped ) /* custom timestamps via id3 */
avpriv_set_pts_info ( st , 33 , 1 , MPEG_TIME_BASE ) ;
else
avpriv_set_pts_info ( st , ist - > pts_wrap_bits , ist - > time_base . num , ist - > time_base . den ) ;
2016-11-05 18:05:31 +02:00
2020-06-04 21:59:43 +08:00
// copy disposition
st - > disposition = ist - > disposition ;
2023-05-23 11:16:59 +01:00
av_dict_copy ( & st - > metadata , ist - > metadata , 0 ) ;
2021-08-24 19:41:16 +02:00
ffstream ( st ) - > need_context_update = 1 ;
2016-11-06 23:23:20 +02:00
return 0 ;
2016-11-05 18:04:00 +02:00
}
2016-07-28 00:00:37 +03:00
/* add new subdemuxer streams to our context, if any */
static int update_streams_from_subdemuxer ( AVFormatContext * s , struct playlist * pls )
{
2016-11-06 23:23:20 +02:00
int err ;
2016-07-28 00:00:37 +03:00
while ( pls - > n_main_streams < pls - > ctx - > nb_streams ) {
int ist_idx = pls - > n_main_streams ;
AVStream * st = avformat_new_stream ( s , NULL ) ;
AVStream * ist = pls - > ctx - > streams [ ist_idx ] ;
if ( ! st )
return AVERROR ( ENOMEM ) ;
st - > id = pls - > index ;
dynarray_add ( & pls - > main_streams , & pls - > n_main_streams , st ) ;
add_stream_to_programs ( s , pls , st ) ;
2016-11-06 23:23:20 +02:00
err = set_stream_info_from_input_stream ( st , pls , ist ) ;
if ( err < 0 )
return err ;
2016-07-28 00:00:37 +03:00
}
return 0 ;
}
2016-07-26 11:33:38 +03:00
static void update_noheader_flag ( AVFormatContext * s )
{
HLSContext * c = s - > priv_data ;
int flag_needed = 0 ;
int i ;
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
if ( pls - > has_noheader_flag ) {
flag_needed = 1 ;
break ;
}
}
if ( flag_needed )
s - > ctx_flags | = AVFMTCTX_NOHEADER ;
else
s - > ctx_flags & = ~ AVFMTCTX_NOHEADER ;
}
2016-11-07 00:09:54 +01:00
static int hls_close ( AVFormatContext * s )
{
HLSContext * c = s - > priv_data ;
free_playlist_list ( c ) ;
free_variant_list ( c ) ;
free_rendition_list ( c ) ;
2021-09-22 00:12:00 +05:30
if ( c - > crypto_ctx . aes_ctx )
av_free ( c - > crypto_ctx . aes_ctx ) ;
2016-11-07 00:09:54 +01:00
av_dict_free ( & c - > avio_opts ) ;
2017-10-04 14:52:52 -07:00
ff_format_io_close ( c - > ctx , & c - > playlist_pb ) ;
2016-11-07 00:09:54 +01:00
return 0 ;
}
2012-02-14 11:50:51 +02:00
static int hls_read_header ( AVFormatContext * s )
2010-08-19 14:54:37 +00:00
{
2012-02-14 11:50:51 +02:00
HLSContext * c = s - > priv_data ;
2016-07-28 00:00:37 +03:00
int ret = 0 , i ;
2021-01-16 11:40:36 +08:00
int64_t highest_cur_seq_no = 0 ;
2010-08-19 14:54:37 +00:00
2016-01-16 17:53:43 +01:00
c - > ctx = s ;
2011-11-06 22:34:24 +02:00
c - > interrupt_callback = & s - > interrupt_callback ;
2013-12-30 11:27:36 +02:00
c - > first_packet = 1 ;
c - > first_timestamp = AV_NOPTS_VALUE ;
c - > cur_timestamp = AV_NOPTS_VALUE ;
2021-12-14 16:35:14 -08:00
if ( ( ret = ffio_copy_url_options ( s - > pb , & c - > avio_opts ) ) < 0 )
2020-03-21 18:31:06 +01:00
return ret ;
2015-07-23 21:11:58 +02:00
2019-08-08 00:12:16 +08:00
/* XXX: Some HLS servers don't like being sent the range header,
2025-02-10 08:33:40 +08:00
in this case , we need to set http_seekable = 0 to disable
2019-08-08 00:12:16 +08:00
the range header */
av_dict_set_int ( & c - > avio_opts , " seekable " , c - > http_seekable , 0 ) ;
2015-07-30 17:42:19 +02:00
2018-04-17 14:40:06 +08:00
if ( ( ret = parse_playlist ( c , s - > url , NULL , s - > pb ) ) < 0 )
2020-03-21 18:31:06 +01:00
return ret ;
2018-04-17 14:40:06 +08:00
2010-08-19 14:54:37 +00:00
if ( c - > n_variants = = 0 ) {
2019-06-01 18:45:35 +08:00
av_log ( s , AV_LOG_WARNING , " Empty playlist \n " ) ;
2020-03-21 18:31:06 +01:00
return AVERROR_EOF ;
2010-08-19 14:54:37 +00:00
}
2013-12-27 13:01:10 +02:00
/* If the playlist only contained playlists (Master Playlist),
* parse each individual playlist . */
if ( c - > n_playlists > 1 | | c - > playlists [ 0 ] - > n_segments = = 0 ) {
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
2019-11-18 17:37:00 +08:00
pls - > m3u8_hold_counters = 0 ;
2019-09-03 09:55:17 +08:00
if ( ( ret = parse_playlist ( c , pls - > url , pls , NULL ) ) < 0 ) {
av_log ( s , AV_LOG_WARNING , " parse_playlist error %s [%s] \n " , av_err2str ( ret ) , pls - > url ) ;
pls - > broken = 1 ;
if ( c - > n_playlists > 1 )
continue ;
2020-03-21 18:31:06 +01:00
return ret ;
2019-09-03 09:55:17 +08:00
}
2010-08-19 14:54:37 +00:00
}
}
2019-09-03 09:55:17 +08:00
for ( i = 0 ; i < c - > n_variants ; i + + ) {
if ( c - > variants [ i ] - > playlists [ 0 ] - > n_segments = = 0 ) {
av_log ( s , AV_LOG_WARNING , " Empty segment [%s] \n " , c - > variants [ i ] - > playlists [ 0 ] - > url ) ;
c - > variants [ i ] - > playlists [ 0 ] - > broken = 1 ;
}
2010-08-19 14:54:37 +00:00
}
/* If this isn't a live stream, calculate the total duration of the
* stream . */
2013-12-27 13:01:10 +02:00
if ( c - > variants [ 0 ] - > playlists [ 0 ] - > finished ) {
2013-07-22 19:36:33 +02:00
int64_t duration = 0 ;
2013-12-27 13:01:10 +02:00
for ( i = 0 ; i < c - > variants [ 0 ] - > playlists [ 0 ] - > n_segments ; i + + )
duration + = c - > variants [ 0 ] - > playlists [ 0 ] - > segments [ i ] - > duration ;
2013-07-22 19:36:33 +02:00
s - > duration = duration ;
2010-08-19 14:54:37 +00:00
}
2013-12-28 00:09:45 +02:00
/* Associate renditions with variants */
for ( i = 0 ; i < c - > n_variants ; i + + ) {
struct variant * var = c - > variants [ i ] ;
if ( var - > audio_group [ 0 ] )
add_renditions_to_variant ( c , var , AVMEDIA_TYPE_AUDIO , var - > audio_group ) ;
if ( var - > video_group [ 0 ] )
add_renditions_to_variant ( c , var , AVMEDIA_TYPE_VIDEO , var - > video_group ) ;
if ( var - > subtitles_group [ 0 ] )
add_renditions_to_variant ( c , var , AVMEDIA_TYPE_SUBTITLE , var - > subtitles_group ) ;
}
2016-07-28 00:00:37 +03:00
/* Create a program for each variant */
for ( i = 0 ; i < c - > n_variants ; i + + ) {
struct variant * v = c - > variants [ i ] ;
AVProgram * program ;
program = av_new_program ( s , i ) ;
if ( ! program )
2020-03-21 18:31:06 +01:00
return AVERROR ( ENOMEM ) ;
2016-07-28 00:00:37 +03:00
av_dict_set_int ( & program - > metadata , " variant_bitrate " , v - > bandwidth , 0 ) ;
}
2016-07-27 22:52:44 +03:00
/* Select the starting segments */
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
if ( pls - > n_segments = = 0 )
continue ;
pls - > cur_seq_no = select_cur_seq_no ( c , pls ) ;
highest_cur_seq_no = FFMAX ( highest_cur_seq_no , pls - > cur_seq_no ) ;
}
2025-02-21 13:14:18 +01:00
av_dict_set ( & c - > seg_format_opts , " prefer_hls_mpegts_pts " , " 1 " , 0 ) ;
2013-12-27 13:01:10 +02:00
/* Open the demuxer for each playlist */
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
2021-02-25 03:11:32 +01:00
const AVInputFormat * in_fmt = NULL ;
2020-06-29 19:49:41 +02:00
char * url ;
2021-09-22 00:12:00 +05:30
AVDictionary * options = NULL ;
struct segment * seg = NULL ;
2012-11-16 14:12:27 +08:00
2020-03-21 18:31:06 +01:00
if ( ! ( pls - > ctx = avformat_alloc_context ( ) ) )
return AVERROR ( ENOMEM ) ;
2011-06-04 17:36:30 +02:00
2015-02-16 19:31:42 +01:00
if ( pls - > n_segments = = 0 )
continue ;
2013-12-27 13:01:10 +02:00
pls - > index = i ;
pls - > needed = 1 ;
pls - > parent = s ;
2016-07-27 22:52:44 +03:00
/*
* If this is a live stream and this playlist looks like it is one segment
* behind , try to sync it up so that every substream starts at the same
* time position ( so e . g . avformat_find_stream_info ( ) will see packets from
* all active streams within the first few seconds ) . This is not very generic ,
* though , as the sequence numbers are technically independent .
*/
if ( ! pls - > finished & & pls - > cur_seq_no = = highest_cur_seq_no - 1 & &
highest_cur_seq_no < pls - > start_seq_no + pls - > n_segments ) {
pls - > cur_seq_no = highest_cur_seq_no ;
}
2011-03-21 00:21:56 +02:00
2013-12-27 13:01:10 +02:00
pls - > read_buffer = av_malloc ( INITIAL_BUFFER_SIZE ) ;
2015-05-12 19:03:18 +02:00
if ( ! pls - > read_buffer ) {
avformat_free_context ( pls - > ctx ) ;
pls - > ctx = NULL ;
2020-03-21 18:31:06 +01:00
return AVERROR ( ENOMEM ) ;
2015-05-12 19:03:18 +02:00
}
2021-09-22 00:12:00 +05:30
2025-02-21 13:14:18 +01:00
if ( pls - > is_subtitle )
ffio_init_context ( & pls - > pb , ( unsigned char * ) av_strdup ( " WEBVTT \n " ) , ( int ) strlen ( " WEBVTT \n " ) , 0 , pls ,
NULL , NULL , NULL ) ;
else
ffio_init_context ( & pls - > pb , pls - > read_buffer , INITIAL_BUFFER_SIZE , 0 , pls ,
read_data_continuous , NULL , NULL ) ;
2021-09-22 00:12:00 +05:30
/*
* If encryption scheme is SAMPLE - AES , try to read ID3 tags of
* external audio track that contains audio setup information
*/
seg = current_segment ( pls ) ;
if ( seg & & seg - > key_type = = KEY_SAMPLE_AES & & pls - > n_renditions > 0 & &
pls - > renditions [ 0 ] - > type = = AVMEDIA_TYPE_AUDIO ) {
uint8_t buf [ HLS_MAX_ID3_TAGS_DATA_LEN ] ;
if ( ( ret = avio_read ( & pls - > pb . pub , buf , HLS_MAX_ID3_TAGS_DATA_LEN ) ) < 0 ) {
/* Fail if error was not end of file */
if ( ret ! = AVERROR_EOF ) {
avformat_free_context ( pls - > ctx ) ;
pls - > ctx = NULL ;
return ret ;
}
}
ret = 0 ;
/* Reset reading */
ff_format_io_close ( pls - > parent , & pls - > input ) ;
pls - > input = NULL ;
pls - > input_read_done = 0 ;
ff_format_io_close ( pls - > parent , & pls - > input_next ) ;
pls - > input_next = NULL ;
pls - > input_next_requested = 0 ;
pls - > cur_seg_offset = 0 ;
pls - > cur_init_section = NULL ;
/* Reset EOF flag */
pls - > pb . pub . eof_reached = 0 ;
/* Clear any buffered data */
pls - > pb . pub . buf_end = pls - > pb . pub . buf_ptr = pls - > pb . pub . buffer ;
/* Reset the position */
pls - > pb . pub . pos = 0 ;
}
/*
* If encryption scheme is SAMPLE - AES and audio setup information is present in external audio track ,
* use that information to find the media format , otherwise probe input data
*/
2024-03-17 03:36:16 +01:00
seg = current_segment ( pls ) ;
2021-09-22 00:12:00 +05:30
if ( seg & & seg - > key_type = = KEY_SAMPLE_AES & & pls - > is_id3_timestamped & &
pls - > audio_setup_info . codec_id ! = AV_CODEC_ID_NONE ) {
2024-03-18 00:24:39 +01:00
av_assert1 ( pls - > audio_setup_info . codec_id = = AV_CODEC_ID_AAC | |
pls - > audio_setup_info . codec_id = = AV_CODEC_ID_AC3 | |
pls - > audio_setup_info . codec_id = = AV_CODEC_ID_EAC3 ) ;
// Keep this list in sync with ff_hls_senc_read_audio_setup_info()
in_fmt = av_find_input_format ( pls - > audio_setup_info . codec_id = = AV_CODEC_ID_AAC ? " aac " :
pls - > audio_setup_info . codec_id = = AV_CODEC_ID_AC3 ? " ac3 " : " eac3 " ) ;
2021-09-22 00:12:00 +05:30
} else {
2021-09-22 00:12:28 +05:30
pls - > ctx - > probesize = s - > probesize > 0 ? s - > probesize : 1024 * 4 ;
pls - > ctx - > max_analyze_duration = s - > max_analyze_duration > 0 ? s - > max_analyze_duration : 4 * AV_TIME_BASE ;
pls - > ctx - > interrupt_callback = s - > interrupt_callback ;
url = av_strdup ( pls - > segments [ 0 ] - > url ) ;
ret = av_probe_input_buffer ( & pls - > pb . pub , & in_fmt , url , NULL , 0 , 0 ) ;
2025-01-16 01:28:46 +01:00
for ( int n = 0 ; n < pls - > n_segments ; n + + )
if ( ret > = 0 )
ret = test_segment ( s , in_fmt , pls , pls - > segments [ n ] ) ;
2021-09-22 00:12:28 +05:30
if ( ret < 0 ) {
/* Free the ctx - it isn't initialized properly at this point,
* so avformat_close_input shouldn ' t be called . If
* avformat_open_input fails below , it frees and zeros the
* context , so it doesn ' t need any special treatment like this . */
av_log ( s , AV_LOG_ERROR , " Error when loading first segment '%s' \n " , url ) ;
avformat_free_context ( pls - > ctx ) ;
pls - > ctx = NULL ;
av_free ( url ) ;
return ret ;
}
2020-10-19 10:07:57 +08:00
av_free ( url ) ;
2021-09-22 00:12:00 +05:30
}
2024-03-17 03:36:16 +01:00
seg = current_segment ( pls ) ;
2021-09-22 00:12:00 +05:30
if ( seg & & seg - > key_type = = KEY_SAMPLE_AES ) {
if ( strstr ( in_fmt - > name , " mov " ) ) {
char key [ 33 ] ;
ff_data_to_hex ( key , pls - > key , sizeof ( pls - > key ) , 0 ) ;
2022-05-15 19:32:44 +02:00
av_dict_set ( & options , " decryption_key " , key , 0 ) ;
2021-09-22 00:12:00 +05:30
} else if ( ! c - > crypto_ctx . aes_ctx ) {
c - > crypto_ctx . aes_ctx = av_aes_alloc ( ) ;
if ( ! c - > crypto_ctx . aes_ctx ) {
avformat_free_context ( pls - > ctx ) ;
pls - > ctx = NULL ;
return AVERROR ( ENOMEM ) ;
}
}
}
2021-08-04 16:52:07 +02:00
pls - > ctx - > pb = & pls - > pb . pub ;
2016-02-29 15:50:32 +00:00
pls - > ctx - > io_open = nested_io_open ;
2017-05-22 17:31:32 +02:00
pls - > ctx - > flags | = s - > flags & ~ AVFMT_FLAG_CUSTOM_IO ;
2014-10-23 15:26:46 +02:00
2016-03-03 17:14:26 +00:00
if ( ( ret = ff_copy_whiteblacklists ( pls - > ctx , s ) ) < 0 )
2020-03-21 18:31:06 +01:00
return ret ;
2014-10-23 15:26:46 +02:00
2021-09-22 00:12:00 +05:30
av_dict_copy ( & options , c - > seg_format_opts , 0 ) ;
2021-05-29 15:27:18 +05:30
2021-09-22 00:12:00 +05:30
ret = avformat_open_input ( & pls - > ctx , pls - > segments [ 0 ] - > url , in_fmt , & options ) ;
av_dict_free ( & options ) ;
2012-07-25 17:40:33 +02:00
if ( ret < 0 )
2020-03-21 18:31:06 +01:00
return ret ;
2012-07-25 17:40:33 +02:00
2014-04-05 14:47:26 +03:00
if ( pls - > id3_deferred_extra & & pls - > ctx - > nb_streams = = 1 ) {
2020-05-19 12:09:48 +02:00
ff_id3v2_parse_apic ( pls - > ctx , pls - > id3_deferred_extra ) ;
2014-04-05 14:47:26 +03:00
avformat_queue_attached_pictures ( pls - > ctx ) ;
2020-05-19 12:09:48 +02:00
ff_id3v2_parse_priv ( pls - > ctx , pls - > id3_deferred_extra ) ;
2014-04-05 14:47:26 +03:00
ff_id3v2_free_extra_meta ( & pls - > id3_deferred_extra ) ;
}
if ( pls - > is_id3_timestamped = = - 1 )
av_log ( s , AV_LOG_WARNING , " No expected HTTP requests have been made \n " ) ;
2016-07-26 11:33:38 +03:00
/*
* For ID3 timestamped raw audio streams we need to detect the packet
* durations to calculate timestamps in fill_timing_for_id3_timestamped_stream ( ) ,
* but for other streams we can rely on our user calling avformat_find_stream_info ( )
* on us if they want to .
*/
2019-06-04 15:27:11 +08:00
if ( pls - > is_id3_timestamped | | ( pls - > n_renditions > 0 & & pls - > renditions [ 0 ] - > type = = AVMEDIA_TYPE_AUDIO ) ) {
2024-03-17 03:36:16 +01:00
seg = current_segment ( pls ) ;
2021-09-22 00:12:00 +05:30
if ( seg & & seg - > key_type = = KEY_SAMPLE_AES & & pls - > audio_setup_info . setup_data_length > 0 & &
pls - > ctx - > nb_streams = = 1 )
ret = ff_hls_senc_parse_audio_setup_info ( pls - > ctx - > streams [ 0 ] , & pls - > audio_setup_info ) ;
else
2021-09-22 00:12:28 +05:30
ret = avformat_find_stream_info ( pls - > ctx , NULL ) ;
2021-09-22 00:12:00 +05:30
2016-07-26 11:33:38 +03:00
if ( ret < 0 )
2020-03-21 18:31:06 +01:00
return ret ;
2016-07-26 11:33:38 +03:00
}
pls - > has_noheader_flag = ! ! ( pls - > ctx - > ctx_flags & AVFMTCTX_NOHEADER ) ;
2013-12-27 13:01:10 +02:00
/* Create new AVStreams for each stream in this playlist */
2016-07-28 00:00:37 +03:00
ret = update_streams_from_subdemuxer ( s , pls ) ;
if ( ret < 0 )
2020-03-21 18:31:06 +01:00
return ret ;
2013-12-27 13:01:10 +02:00
2018-02-02 12:59:45 -08:00
/*
* Copy any metadata from playlist to main streams , but do not set
* event flags .
*/
if ( pls - > n_main_streams )
av_dict_copy ( & pls - > main_streams [ 0 ] - > metadata , pls - > ctx - > metadata , 0 ) ;
2025-02-21 13:14:18 +01:00
if ( pls - > is_subtitle ) {
avformat_free_context ( pls - > ctx ) ;
pls - > ctx = NULL ;
pls - > needed = 0 ;
pls - > main_streams [ 0 ] - > discard = AVDISCARD_ALL ;
}
2013-12-28 00:09:45 +02:00
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_AUDIO ) ;
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_VIDEO ) ;
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_SUBTITLE ) ;
2013-12-27 13:01:10 +02:00
}
2016-07-26 11:33:38 +03:00
update_noheader_flag ( s ) ;
2010-08-19 14:54:37 +00:00
return 0 ;
}
2011-03-21 00:21:56 +02:00
static int recheck_discard_flags ( AVFormatContext * s , int first )
2010-08-19 14:54:37 +00:00
{
2012-02-14 11:50:51 +02:00
HLSContext * c = s - > priv_data ;
2011-03-21 00:21:56 +02:00
int i , changed = 0 ;
2017-11-19 19:30:02 +02:00
int cur_needed ;
2010-08-19 14:54:37 +00:00
2011-03-21 00:21:56 +02:00
/* Check if any new streams are needed */
2013-12-27 13:01:10 +02:00
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
2017-11-19 19:30:02 +02:00
cur_needed = playlist_needed ( c - > playlists [ i ] ) ;
2019-09-03 09:55:17 +08:00
if ( pls - > broken ) {
continue ;
}
2017-11-19 19:30:02 +02:00
if ( cur_needed & & ! pls - > needed ) {
2013-12-27 13:01:10 +02:00
pls - > needed = 1 ;
2011-03-21 00:21:56 +02:00
changed = 1 ;
2013-12-30 11:27:36 +02:00
pls - > cur_seq_no = select_cur_seq_no ( c , pls ) ;
2021-08-04 16:52:07 +02:00
pls - > pb . pub . eof_reached = 0 ;
2013-12-30 11:27:36 +02:00
if ( c - > cur_timestamp ! = AV_NOPTS_VALUE ) {
/* catch up */
pls - > seek_timestamp = c - > cur_timestamp ;
pls - > seek_flags = AVSEEK_FLAG_ANY ;
2014-01-03 13:44:38 +02:00
pls - > seek_stream_index = - 1 ;
2013-12-30 11:27:36 +02:00
}
2021-01-16 11:40:36 +08:00
av_log ( s , AV_LOG_INFO , " Now receiving playlist %d, segment % " PRId64 " \n " , i , pls - > cur_seq_no ) ;
2017-11-19 19:30:02 +02:00
} else if ( first & & ! cur_needed & & pls - > needed ) {
2019-09-13 19:53:35 +08:00
ff_format_io_close ( pls - > parent , & pls - > input ) ;
2017-10-04 14:52:52 -07:00
pls - > input_read_done = 0 ;
2019-09-13 19:53:35 +08:00
ff_format_io_close ( pls - > parent , & pls - > input_next ) ;
2017-12-12 15:25:46 -08:00
pls - > input_next_requested = 0 ;
2025-02-21 13:14:18 +01:00
if ( pls - > is_subtitle )
avformat_close_input ( & pls - > ctx ) ;
2013-12-27 13:01:10 +02:00
pls - > needed = 0 ;
2011-03-21 00:21:56 +02:00
changed = 1 ;
2013-12-27 13:01:10 +02:00
av_log ( s , AV_LOG_INFO , " No longer receiving playlist %d \n " , i ) ;
2010-08-19 14:54:37 +00:00
}
}
2011-03-21 00:21:56 +02:00
return changed ;
2010-08-19 14:54:37 +00:00
}
2014-04-05 14:47:26 +03:00
static void fill_timing_for_id3_timestamped_stream ( struct playlist * pls )
{
if ( pls - > id3_offset > = 0 ) {
2021-01-29 10:27:05 -03:00
pls - > pkt - > dts = pls - > id3_mpegts_timestamp +
2014-04-05 14:47:26 +03:00
av_rescale_q ( pls - > id3_offset ,
2021-01-29 10:27:05 -03:00
pls - > ctx - > streams [ pls - > pkt - > stream_index ] - > time_base ,
2014-04-05 14:47:26 +03:00
MPEG_TIME_BASE_Q ) ;
2021-01-29 10:27:05 -03:00
if ( pls - > pkt - > duration )
pls - > id3_offset + = pls - > pkt - > duration ;
2014-04-05 14:47:26 +03:00
else
pls - > id3_offset = - 1 ;
} else {
/* there have been packets with unknown duration
* since the last id3 tag , should not normally happen */
2021-01-29 10:27:05 -03:00
pls - > pkt - > dts = AV_NOPTS_VALUE ;
2014-04-05 14:47:26 +03:00
}
2021-01-29 10:27:05 -03:00
if ( pls - > pkt - > duration )
pls - > pkt - > duration = av_rescale_q ( pls - > pkt - > duration ,
pls - > ctx - > streams [ pls - > pkt - > stream_index ] - > time_base ,
2014-04-05 14:47:26 +03:00
MPEG_TIME_BASE_Q ) ;
2021-01-29 10:27:05 -03:00
pls - > pkt - > pts = AV_NOPTS_VALUE ;
2014-04-05 14:47:26 +03:00
}
static AVRational get_timebase ( struct playlist * pls )
{
if ( pls - > is_id3_timestamped )
return MPEG_TIME_BASE_Q ;
2021-01-29 10:27:05 -03:00
return pls - > ctx - > streams [ pls - > pkt - > stream_index ] - > time_base ;
2014-04-05 14:47:26 +03:00
}
2013-12-30 11:53:56 +02:00
static int compare_ts_with_wrapdetect ( int64_t ts_a , struct playlist * pls_a ,
int64_t ts_b , struct playlist * pls_b )
{
int64_t scaled_ts_a = av_rescale_q ( ts_a , get_timebase ( pls_a ) , MPEG_TIME_BASE_Q ) ;
int64_t scaled_ts_b = av_rescale_q ( ts_b , get_timebase ( pls_b ) , MPEG_TIME_BASE_Q ) ;
return av_compare_mod ( scaled_ts_a , scaled_ts_b , 1LL < < 33 ) ;
}
2012-02-14 11:50:51 +02:00
static int hls_read_packet ( AVFormatContext * s , AVPacket * pkt )
2010-08-19 14:54:37 +00:00
{
2012-02-14 11:50:51 +02:00
HLSContext * c = s - > priv_data ;
2013-12-27 13:01:10 +02:00
int ret , i , minplaylist = - 1 ;
2010-08-19 14:54:37 +00:00
2014-01-03 10:51:55 +02:00
recheck_discard_flags ( s , c - > first_packet ) ;
2015-10-15 13:42:38 +03:00
c - > first_packet = 0 ;
2011-03-21 00:21:56 +02:00
2013-12-27 13:01:10 +02:00
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
/* Make sure we've got one buffered packet from each open playlist
2010-08-19 14:54:37 +00:00
* stream */
2021-01-29 10:27:05 -03:00
if ( pls - > needed & & ! pls - > pkt - > data ) {
2012-01-10 14:48:57 +01:00
while ( 1 ) {
int64_t ts_diff ;
2014-04-05 14:47:26 +03:00
AVRational tb ;
2021-09-22 00:12:00 +05:30
struct segment * seg = NULL ;
2025-02-21 13:14:18 +01:00
if ( pls - > is_subtitle )
ret = read_subtitle_packet ( pls , pls - > pkt ) ;
else
ret = av_read_frame ( pls - > ctx , pls - > pkt ) ;
2012-01-10 14:48:57 +01:00
if ( ret < 0 ) {
2021-08-04 16:52:07 +02:00
if ( ! avio_feof ( & pls - > pb . pub ) & & ret ! = AVERROR_EOF )
2012-01-10 14:48:57 +01:00
return ret ;
2012-01-10 14:48:57 +01:00
break ;
2012-01-10 14:48:57 +01:00
} else {
2014-04-05 14:47:26 +03:00
/* stream_index check prevents matching picture attachments etc. */
2021-01-29 10:27:05 -03:00
if ( pls - > is_id3_timestamped & & pls - > pkt - > stream_index = = 0 ) {
2014-04-05 14:47:26 +03:00
/* audio elementary streams are id3 timestamped */
fill_timing_for_id3_timestamped_stream ( pls ) ;
}
2013-07-29 10:09:02 +03:00
if ( c - > first_timestamp = = AV_NOPTS_VALUE & &
2021-01-29 10:27:05 -03:00
pls - > pkt - > dts ! = AV_NOPTS_VALUE )
c - > first_timestamp = av_rescale_q ( pls - > pkt - > dts ,
2014-04-05 14:47:26 +03:00
get_timebase ( pls ) , AV_TIME_BASE_Q ) ;
2012-01-10 14:48:57 +01:00
}
2021-09-22 00:12:00 +05:30
seg = current_segment ( pls ) ;
if ( seg & & seg - > key_type = = KEY_SAMPLE_AES & & ! strstr ( pls - > ctx - > iformat - > name , " mov " ) ) {
enum AVCodecID codec_id = pls - > ctx - > streams [ pls - > pkt - > stream_index ] - > codecpar - > codec_id ;
memcpy ( c - > crypto_ctx . iv , seg - > iv , sizeof ( seg - > iv ) ) ;
memcpy ( c - > crypto_ctx . key , pls - > key , sizeof ( pls - > key ) ) ;
ff_hls_senc_decrypt_frame ( codec_id , & c - > crypto_ctx , pls - > pkt ) ;
}
2013-12-30 11:13:56 +02:00
if ( pls - > seek_timestamp = = AV_NOPTS_VALUE )
2012-01-10 14:48:57 +01:00
break ;
2014-01-03 13:44:38 +02:00
if ( pls - > seek_stream_index < 0 | |
2021-01-29 10:27:05 -03:00
pls - > seek_stream_index = = pls - > pkt - > stream_index ) {
2012-01-10 14:48:57 +01:00
2021-01-29 10:27:05 -03:00
if ( pls - > pkt - > dts = = AV_NOPTS_VALUE ) {
2014-01-03 13:44:38 +02:00
pls - > seek_timestamp = AV_NOPTS_VALUE ;
break ;
}
tb = get_timebase ( pls ) ;
2021-01-29 10:27:05 -03:00
ts_diff = av_rescale_rnd ( pls - > pkt - > dts , AV_TIME_BASE ,
2014-01-03 13:44:38 +02:00
tb . den , AV_ROUND_DOWN ) -
pls - > seek_timestamp ;
if ( ts_diff > = 0 & & ( pls - > seek_flags & AVSEEK_FLAG_ANY | |
2021-01-29 10:27:05 -03:00
pls - > pkt - > flags & AV_PKT_FLAG_KEY ) ) {
2014-01-03 13:44:38 +02:00
pls - > seek_timestamp = AV_NOPTS_VALUE ;
break ;
}
2012-01-10 14:48:57 +01:00
}
2021-01-29 10:27:05 -03:00
av_packet_unref ( pls - > pkt ) ;
2010-08-19 14:54:37 +00:00
}
}
2013-12-30 11:53:56 +02:00
/* Check if this stream has the packet with the lowest dts */
2021-01-29 10:27:05 -03:00
if ( pls - > pkt - > data ) {
2013-12-27 13:01:10 +02:00
struct playlist * minpls = minplaylist < 0 ?
NULL : c - > playlists [ minplaylist ] ;
2013-12-30 11:53:56 +02:00
if ( minplaylist < 0 ) {
2013-12-27 13:01:10 +02:00
minplaylist = i ;
2013-12-30 11:53:56 +02:00
} else {
2021-01-29 10:27:05 -03:00
int64_t dts = pls - > pkt - > dts ;
int64_t mindts = minpls - > pkt - > dts ;
2012-08-21 01:48:04 +02:00
2013-12-30 11:53:56 +02:00
if ( dts = = AV_NOPTS_VALUE | |
( mindts ! = AV_NOPTS_VALUE & & compare_ts_with_wrapdetect ( dts , pls , mindts , minpls ) < 0 ) )
2013-12-27 13:01:10 +02:00
minplaylist = i ;
2012-08-21 01:48:04 +02:00
}
2010-08-19 14:54:37 +00:00
}
}
2014-01-03 10:51:55 +02:00
2010-08-19 14:54:37 +00:00
/* If we got a packet, return it */
2013-12-27 13:01:10 +02:00
if ( minplaylist > = 0 ) {
2013-12-30 11:27:36 +02:00
struct playlist * pls = c - > playlists [ minplaylist ] ;
2016-11-05 18:05:31 +02:00
AVStream * ist ;
AVStream * st ;
2016-07-27 23:29:16 +03:00
2016-07-26 11:33:38 +03:00
ret = update_streams_from_subdemuxer ( s , pls ) ;
if ( ret < 0 ) {
2021-01-29 10:27:05 -03:00
av_packet_unref ( pls - > pkt ) ;
2016-07-26 11:33:38 +03:00
return ret ;
}
2018-02-02 12:59:45 -08:00
// If sub-demuxer reports updated metadata, copy it to the first stream
// and set its AVSTREAM_EVENT_FLAG_METADATA_UPDATED flag.
if ( pls - > ctx - > event_flags & AVFMT_EVENT_FLAG_METADATA_UPDATED ) {
if ( pls - > n_main_streams ) {
st = pls - > main_streams [ 0 ] ;
av_dict_copy ( & st - > metadata , pls - > ctx - > metadata , 0 ) ;
st - > event_flags | = AVSTREAM_EVENT_FLAG_METADATA_UPDATED ;
}
pls - > ctx - > event_flags & = ~ AVFMT_EVENT_FLAG_METADATA_UPDATED ;
}
2016-07-26 11:33:38 +03:00
/* check if noheader flag has been cleared by the subdemuxer */
if ( pls - > has_noheader_flag & & ! ( pls - > ctx - > ctx_flags & AVFMTCTX_NOHEADER ) ) {
pls - > has_noheader_flag = 0 ;
update_noheader_flag ( s ) ;
}
2021-01-29 10:27:05 -03:00
if ( pls - > pkt - > stream_index > = pls - > n_main_streams ) {
2016-07-27 23:29:16 +03:00
av_log ( s , AV_LOG_ERROR , " stream index inconsistency: index %d, %d main streams, %d subdemuxer streams \n " ,
2021-01-29 10:27:05 -03:00
pls - > pkt - > stream_index , pls - > n_main_streams , pls - > ctx - > nb_streams ) ;
av_packet_unref ( pls - > pkt ) ;
2016-07-27 23:29:16 +03:00
return AVERROR_BUG ;
}
2021-01-29 10:27:05 -03:00
ist = pls - > ctx - > streams [ pls - > pkt - > stream_index ] ;
st = pls - > main_streams [ pls - > pkt - > stream_index ] ;
2016-11-05 18:05:31 +02:00
2021-01-29 10:27:05 -03:00
av_packet_move_ref ( pkt , pls - > pkt ) ;
2016-11-05 18:05:31 +02:00
pkt - > stream_index = st - > index ;
2013-12-30 11:27:36 +02:00
if ( pkt - > dts ! = AV_NOPTS_VALUE )
c - > cur_timestamp = av_rescale_q ( pkt - > dts ,
2016-11-05 18:05:31 +02:00
ist - > time_base ,
2013-12-30 11:27:36 +02:00
AV_TIME_BASE_Q ) ;
2016-11-05 18:05:31 +02:00
/* There may be more situations where this would be useful, but this at least
* handles newly probed codecs properly ( i . e . request_probe by mpegts ) . */
2016-11-06 23:23:20 +02:00
if ( ist - > codecpar - > codec_id ! = st - > codecpar - > codec_id ) {
ret = set_stream_info_from_input_stream ( st , pls , ist ) ;
if ( ret < 0 ) {
return ret ;
}
}
2016-11-05 18:05:31 +02:00
2010-08-19 14:54:37 +00:00
return 0 ;
}
2011-03-21 00:21:56 +02:00
return AVERROR_EOF ;
2010-08-19 14:54:37 +00:00
}
2012-02-14 11:50:51 +02:00
static int hls_read_seek ( AVFormatContext * s , int stream_index ,
2010-08-19 14:54:37 +00:00
int64_t timestamp , int flags )
{
2012-02-14 11:50:51 +02:00
HLSContext * c = s - > priv_data ;
2014-01-03 13:44:38 +02:00
struct playlist * seek_pls = NULL ;
2021-01-16 11:40:36 +08:00
int i , j ;
2016-07-27 23:29:16 +03:00
int stream_subdemuxer_index ;
2014-01-05 02:18:12 +04:00
int64_t first_timestamp , seek_timestamp , duration ;
2022-01-28 21:23:20 +01:00
int64_t seq_no , seg_start_ts ;
2010-08-19 14:54:37 +00:00
2018-01-24 08:02:25 +01:00
if ( ( flags & AVSEEK_FLAG_BYTE ) | | ( c - > ctx - > ctx_flags & AVFMTCTX_UNSEEKABLE ) )
2010-08-19 14:54:37 +00:00
return AVERROR ( ENOSYS ) ;
2014-01-05 02:18:12 +04:00
first_timestamp = c - > first_timestamp = = AV_NOPTS_VALUE ?
0 : c - > first_timestamp ;
2014-01-03 13:44:38 +02:00
seek_timestamp = av_rescale_rnd ( timestamp , AV_TIME_BASE ,
2013-12-30 11:13:56 +02:00
s - > streams [ stream_index ] - > time_base . den ,
2022-01-28 21:23:20 +01:00
AV_ROUND_DOWN ) ;
2013-12-30 11:13:56 +02:00
2014-01-05 02:18:12 +04:00
duration = s - > duration = = AV_NOPTS_VALUE ?
0 : s - > duration ;
if ( 0 < duration & & duration < seek_timestamp - first_timestamp )
2012-01-10 14:48:57 +01:00
return AVERROR ( EIO ) ;
2013-12-30 11:13:56 +02:00
2014-01-03 13:44:38 +02:00
/* find the playlist with the specified stream */
2013-12-30 11:13:56 +02:00
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
struct playlist * pls = c - > playlists [ i ] ;
2016-07-27 23:29:16 +03:00
for ( j = 0 ; j < pls - > n_main_streams ; j + + ) {
if ( pls - > main_streams [ j ] = = s - > streams [ stream_index ] ) {
seek_pls = pls ;
stream_subdemuxer_index = j ;
break ;
}
2013-12-30 11:13:56 +02:00
}
2012-01-10 14:48:57 +01:00
}
2014-01-03 13:44:38 +02:00
/* check if the timestamp is valid for the playlist with the
* specified stream index */
2022-01-28 21:23:20 +01:00
if ( ! seek_pls | | ! find_timestamp_in_playlist ( c , seek_pls , seek_timestamp , & seq_no , & seg_start_ts ) )
2013-12-30 11:13:56 +02:00
return AVERROR ( EIO ) ;
2022-01-28 21:23:20 +01:00
if ( s - > streams [ stream_index ] - > codecpar - > codec_type = = AVMEDIA_TYPE_VIDEO & &
flags & AVSEEK_FLAG_BACKWARD & & ! ( flags & AVSEEK_FLAG_ANY ) ) {
/* Seeking to start of segment ensures we seek to a keyframe located
* before the given timestamp . */
seek_timestamp = seg_start_ts ;
}
2014-01-03 13:44:38 +02:00
/* set segment now so we do not need to search again below */
seek_pls - > cur_seq_no = seq_no ;
2016-07-27 23:29:16 +03:00
seek_pls - > seek_stream_index = stream_subdemuxer_index ;
2014-01-03 13:44:38 +02:00
2013-12-27 13:01:10 +02:00
for ( i = 0 ; i < c - > n_playlists ; i + + ) {
2011-03-21 00:23:54 +02:00
/* Reset reading */
2013-12-27 13:01:10 +02:00
struct playlist * pls = c - > playlists [ i ] ;
2021-08-04 16:52:07 +02:00
AVIOContext * const pb = & pls - > pb . pub ;
2019-09-13 19:53:35 +08:00
ff_format_io_close ( pls - > parent , & pls - > input ) ;
2017-10-04 14:52:52 -07:00
pls - > input_read_done = 0 ;
2019-09-13 19:53:35 +08:00
ff_format_io_close ( pls - > parent , & pls - > input_next ) ;
2017-12-12 15:25:46 -08:00
pls - > input_next_requested = 0 ;
2021-01-29 10:27:05 -03:00
av_packet_unref ( pls - > pkt ) ;
2021-08-04 16:52:07 +02:00
pb - > eof_reached = 0 ;
2012-01-20 16:29:09 +02:00
/* Clear any buffered data */
2021-08-04 16:52:07 +02:00
pb - > buf_end = pb - > buf_ptr = pb - > buffer ;
2024-11-04 14:07:42 +00:00
/* Reset the pos, to let the mpegts/mov demuxer know we've seeked. */
2021-08-04 16:52:07 +02:00
pb - > pos = 0 ;
2014-01-03 13:52:20 +02:00
/* Flush the packet queue of the subdemuxer. */
2025-02-21 13:14:18 +01:00
if ( pls - > ctx )
ff_read_frame_flush ( pls - > ctx ) ;
if ( pls - > is_subtitle )
avformat_close_input ( & pls - > ctx ) ;
2010-08-19 14:54:37 +00:00
2025-08-01 22:43:23 +02:00
/* Reset the init segment so it's re-fetched and served appropriately */
2023-08-15 22:50:23 +02:00
pls - > cur_init_section = NULL ;
2013-12-30 11:13:56 +02:00
pls - > seek_timestamp = seek_timestamp ;
pls - > seek_flags = flags ;
2014-01-03 13:44:38 +02:00
if ( pls ! = seek_pls ) {
/* set closest segment seq_no for playlists not handled above */
2022-01-28 21:23:20 +01:00
find_timestamp_in_playlist ( c , pls , seek_timestamp , & pls - > cur_seq_no , NULL ) ;
2014-01-03 13:44:38 +02:00
/* seek the playlist to the given position without taking
* keyframes into account since this playlist does not have the
* specified stream where we should look for the keyframes */
pls - > seek_stream_index = - 1 ;
pls - > seek_flags | = AVSEEK_FLAG_ANY ;
}
2010-08-19 14:54:37 +00:00
}
2013-12-30 11:13:56 +02:00
2013-12-30 11:27:36 +02:00
c - > cur_timestamp = seek_timestamp ;
2013-12-30 11:13:56 +02:00
return 0 ;
2010-08-19 14:54:37 +00:00
}
2019-03-21 01:18:37 +01:00
static int hls_probe ( const AVProbeData * p )
2010-08-19 14:54:37 +00:00
{
/* Require #EXTM3U at the start, and either one of the ones below
* somewhere for a proper match . */
if ( strncmp ( p - > buf , " #EXTM3U " , 7 ) )
return 0 ;
2016-01-16 17:28:34 +01:00
2010-08-19 14:54:37 +00:00
if ( strstr ( p - > buf , " #EXT-X-STREAM-INF: " ) | |
strstr ( p - > buf , " #EXT-X-TARGETDURATION: " ) | |
2023-05-03 13:08:35 +02:00
strstr ( p - > buf , " #EXT-X-MEDIA-SEQUENCE: " ) ) {
2023-05-15 00:56:10 +02:00
int mime_ok = p - > mime_type & & ! (
av_strcasecmp ( p - > mime_type , " application/vnd.apple.mpegurl " ) & &
2023-05-15 21:33:03 +02:00
av_strcasecmp ( p - > mime_type , " audio/mpegurl " )
) ;
int mime_x = p - > mime_type & & ! (
2023-05-15 00:56:10 +02:00
av_strcasecmp ( p - > mime_type , " audio/x-mpegurl " ) & &
av_strcasecmp ( p - > mime_type , " application/x-mpegurl " )
) ;
2023-05-15 21:28:26 +02:00
if ( ! mime_ok & &
2023-05-15 21:33:03 +02:00
! mime_x & &
2023-05-15 21:39:13 +02:00
! av_match_ext ( p - > filename , " m3u8,m3u " ) & &
ff_match_url_ext ( p - > filename , " m3u8,m3u " ) < = 0 ) {
2023-05-15 21:33:03 +02:00
av_log ( NULL , AV_LOG_ERROR , " Not detecting m3u8/hls with non standard extension and non standard mime type \n " ) ;
2023-05-03 13:08:35 +02:00
return 0 ;
}
2023-05-15 21:33:03 +02:00
if ( mime_x )
av_log ( NULL , AV_LOG_WARNING , " mime type is not rfc8216 compliant \n " ) ;
2023-05-03 13:08:35 +02:00
2010-08-19 14:54:37 +00:00
return AVPROBE_SCORE_MAX ;
2023-05-03 13:08:35 +02:00
}
2010-08-19 14:54:37 +00:00
return 0 ;
}
2015-03-28 18:27:35 -06:00
# define OFFSET(x) offsetof(HLSContext, x)
# define FLAGS AV_OPT_FLAG_DECODING_PARAM
static const AVOption hls_options [ ] = {
{ " live_start_index " , " segment index to start live streams at (negative values are from the end) " ,
2015-08-16 19:34:13 -04:00
OFFSET ( live_start_index ) , AV_OPT_TYPE_INT , { . i64 = - 3 } , INT_MIN , INT_MAX , FLAGS } ,
2022-06-23 00:55:38 +08:00
{ " prefer_x_start " , " prefer to use #EXT-X-START if it's in playlist instead of live_start_index " ,
OFFSET ( prefer_x_start ) , AV_OPT_TYPE_BOOL , { . i64 = 0 } , 0 , 1 , FLAGS } ,
2017-06-03 21:20:04 +02:00
{ " allowed_extensions " , " List of file extensions that hls is allowed to access " ,
OFFSET ( allowed_extensions ) , AV_OPT_TYPE_STRING ,
2025-04-06 12:30:04 +02:00
{ . str = " 3gp,aac,avi,ac3,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,vtt,wav,webvtt "
" ,cmfv,cmfa " // Ticket11526 www.nicovideo.jp
2025-04-06 12:43:12 +02:00
" ,ec3 " // part of Ticket11435 (Elisa Viihde (Finnish online recording service))
2025-04-06 12:47:34 +02:00
" ,fmp4 " // https://github.com/yt-dlp/yt-dlp/issues/12700
2025-04-30 01:37:27 +02:00
} ,
INT_MIN , INT_MAX , FLAGS } ,
{ " allowed_segment_extensions " , " List of file extensions that hls is allowed to access " ,
OFFSET ( allowed_segment_extensions ) , AV_OPT_TYPE_STRING ,
{ . str = " 3gp,aac,avi,ac3,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,vtt,wav,webvtt "
" ,cmfv,cmfa " // Ticket11526 www.nicovideo.jp
" ,ec3 " // part of Ticket11435 (Elisa Viihde (Finnish online recording service))
" ,fmp4 " // https://github.com/yt-dlp/yt-dlp/issues/12700
2025-04-30 01:05:18 +02:00
" ,html " // https://flash1.bogulus.cfd/
2025-04-06 12:30:04 +02:00
} ,
2017-06-03 21:20:04 +02:00
INT_MIN , INT_MAX , FLAGS } ,
2025-01-16 01:28:46 +01:00
{ " extension_picky " , " Be picky with all extensions matching " ,
OFFSET ( extension_picky ) , AV_OPT_TYPE_BOOL , { . i64 = 1 } , 0 , 1 , FLAGS } ,
2017-08-26 01:26:58 +02:00
{ " max_reload " , " Maximum number of times a insufficient list is attempted to be reloaded " ,
2025-02-27 16:36:56 +00:00
OFFSET ( max_reload ) , AV_OPT_TYPE_INT , { . i64 = 100 } , 0 , INT_MAX , FLAGS } ,
2019-11-27 19:04:00 +08:00
{ " m3u8_hold_counters " , " The maximum number of times to load m3u8 when it refreshes without new segments " ,
2019-11-18 17:37:00 +08:00
OFFSET ( m3u8_hold_counters ) , AV_OPT_TYPE_INT , { . i64 = 1000 } , 0 , INT_MAX , FLAGS } ,
2017-10-04 14:52:52 -07:00
{ " http_persistent " , " Use persistent HTTP connections " ,
OFFSET ( http_persistent ) , AV_OPT_TYPE_BOOL , { . i64 = 1 } , 0 , 1 , FLAGS } ,
2017-12-12 15:25:46 -08:00
{ " http_multiple " , " Use multiple HTTP connections for fetching segments " ,
2017-12-25 20:37:55 -08:00
OFFSET ( http_multiple ) , AV_OPT_TYPE_BOOL , { . i64 = - 1 } , - 1 , 1 , FLAGS } ,
2019-08-08 00:12:16 +08:00
{ " http_seekable " , " Use HTTP partial requests, 0 = disable, 1 = enable, -1 = auto " ,
OFFSET ( http_seekable ) , AV_OPT_TYPE_BOOL , { . i64 = - 1 } , - 1 , 1 , FLAGS } ,
2021-05-29 15:27:18 +05:30
{ " seg_format_options " , " Set options for segment demuxer " ,
OFFSET ( seg_format_opts ) , AV_OPT_TYPE_DICT , { . str = NULL } , 0 , 0 , FLAGS } ,
2022-10-20 20:11:38 +08:00
{ " seg_max_retry " , " Maximum number of times to reload a segment on error. " ,
OFFSET ( seg_max_retry ) , AV_OPT_TYPE_INT , { . i64 = 0 } , 0 , INT_MAX , FLAGS } ,
2015-03-28 18:27:35 -06:00
{ NULL }
} ;
static const AVClass hls_class = {
2019-04-12 18:54:47 +08:00
. class_name = " hls demuxer " ,
2024-01-19 13:33:28 +01:00
. item_name = av_default_item_name ,
2015-03-28 18:27:35 -06:00
. option = hls_options ,
. version = LIBAVUTIL_VERSION_INT ,
} ;
2024-02-10 15:50:43 +01:00
const FFInputFormat ff_hls_demuxer = {
. p . name = " hls " ,
. p . long_name = NULL_IF_CONFIG_SMALL ( " Apple HTTP Live Streaming " ) ,
. p . priv_class = & hls_class ,
. p . flags = AVFMT_NOGENSEARCH | AVFMT_TS_DISCONT | AVFMT_NO_BYTE_SEEK ,
2012-02-14 11:50:51 +02:00
. priv_data_size = sizeof ( HLSContext ) ,
2024-03-15 18:08:11 +01:00
. flags_internal = FF_INFMT_FLAG_INIT_CLEANUP ,
2012-02-14 11:50:51 +02:00
. read_probe = hls_probe ,
. read_header = hls_read_header ,
. read_packet = hls_read_packet ,
. read_close = hls_close ,
. read_seek = hls_read_seek ,
2010-08-19 14:54:37 +00:00
} ;