uint32_t client_report_size; ///< number of bytes after which client should report to server
uint32_t bytes_read; ///< number of bytes read from server
uint32_t last_bytes_read; ///< number of bytes read last reported to server
+ uint32_t last_timestamp; ///< last timestamp received in a packet
int skip_bytes; ///< number of bytes to skip from the input FLV stream in the next write call
int has_audio; ///< presence of audio data
int has_video; ///< presence of video data
return rtmp_send_packet(rt, &pkt, 1);
}
+/**
+ * Generate a pause packet that either pauses or unpauses the current stream.
+ */
+static int gen_pause(URLContext *s, RTMPContext *rt, int pause, uint32_t timestamp)
+{
+ RTMPPacket pkt;
+ uint8_t *p;
+ int ret;
+
+ av_log(s, AV_LOG_DEBUG, "Sending pause command for timestamp %d\n",
+ timestamp);
+
+ if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 29)) < 0)
+ return ret;
+
+ pkt.extra = rt->stream_id;
+
+ p = pkt.data;
+ ff_amf_write_string(&p, "pause");
+ ff_amf_write_number(&p, 0); //no tracking back responses
+ ff_amf_write_null(&p); //as usual, the first null param
+ ff_amf_write_bool(&p, pause); // pause or unpause
+ ff_amf_write_number(&p, timestamp); //where we pause the stream
+
+ return rtmp_send_packet(rt, &pkt, 1);
+}
+
/**
* Generate 'publish' call and send it to the server.
*/
return AVERROR(EIO);
}
}
+
+ // Track timestamp for later use
+ rt->last_timestamp = rpkt.timestamp;
+
rt->bytes_read += ret;
if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) {
av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n");
{
RTMPContext *rt = s->priv_data;
char proto[8], hostname[256], path[1024], auth[100], *fname;
- char *old_app, *qmark, fname_buffer[1024];
+ char *old_app, *qmark, *n, fname_buffer[1024];
uint8_t buf[2048];
int port;
AVDictionary *opts = NULL;
hostname, sizeof(hostname), &port,
path, sizeof(path), s->filename);
- if (strchr(path, ' ')) {
+ n = strchr(path, ' ');
+ if (n) {
av_log(s, AV_LOG_WARNING,
"Detected librtmp style URL parameters, these aren't supported "
"by the libavformat internal RTMP handler currently enabled. "
"See the documentation for the correct way to pass parameters.\n");
+ *n = '\0'; // Trim not supported part
}
if (auth[0]) {
char *next = *path ? path + 1 : path;
char *p = strchr(next, '/');
if (!p) {
- fname = next;
- rt->app[0] = '\0';
+ if (old_app) {
+ // If name of application has been defined by the user, assume that
+ // playpath is provided in the URL
+ fname = next;
+ } else {
+ fname = NULL;
+ av_strlcpy(rt->app, next, APP_MAX_LENGTH);
+ }
} else {
// make sure we do not mismatch a playpath for an application instance
char *c = strchr(p + 1, ':');
}
if (!rt->playpath) {
- int len = strlen(fname);
-
rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH);
if (!rt->playpath) {
ret = AVERROR(ENOMEM);
goto fail;
}
- if (!strchr(fname, ':') && len >= 4 &&
- (!strcmp(fname + len - 4, ".f4v") ||
- !strcmp(fname + len - 4, ".mp4"))) {
- memcpy(rt->playpath, "mp4:", 5);
+ if (fname) {
+ int len = strlen(fname);
+ if (!strchr(fname, ':') && len >= 4 &&
+ (!strcmp(fname + len - 4, ".f4v") ||
+ !strcmp(fname + len - 4, ".mp4"))) {
+ memcpy(rt->playpath, "mp4:", 5);
+ } else {
+ if (len >= 4 && !strcmp(fname + len - 4, ".flv"))
+ fname[len - 4] = '\0';
+ rt->playpath[0] = 0;
+ }
+ av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH);
} else {
- if (len >= 4 && !strcmp(fname + len - 4, ".flv"))
- fname[len - 4] = '\0';
- rt->playpath[0] = 0;
+ rt->playpath[0] = '\0';
}
- av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH);
}
if (!rt->tcurl) {
}
if (rt->is_input) {
- int err;
// generate FLV header for demuxer
rt->flv_size = 13;
- if ((err = av_reallocp(&rt->flv_data, rt->flv_size)) < 0)
- return err;
+ if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0)
+ goto fail;
rt->flv_off = 0;
memcpy(rt->flv_data, "FLV\1\0\0\0\0\011\0\0\0\0", rt->flv_size);
return timestamp;
}
+static int rtmp_pause(URLContext *s, int pause)
+{
+ RTMPContext *rt = s->priv_data;
+ int ret;
+ av_log(s, AV_LOG_DEBUG, "Pause at timestamp %d\n",
+ rt->last_timestamp);
+ if ((ret = gen_pause(s, rt, pause, rt->last_timestamp)) < 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to send pause command at timestamp %d\n",
+ rt->last_timestamp);
+ return ret;
+ }
+ return 0;
+}
+
static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
{
RTMPContext *rt = s->priv_data;
.url_open = rtmp_open, \
.url_read = rtmp_read, \
.url_read_seek = rtmp_seek, \
+ .url_read_pause = rtmp_pause, \
.url_write = rtmp_write, \
.url_close = rtmp_close, \
.priv_data_size = sizeof(RTMPContext), \