I am live streaming a Camera via RTMP to a Nginx RTMP server like so:
ffmpeg -rtsp_transport tcp -i rtsp://user:[email protected]/ -f image2 -stream_loop -1 -re -i /tmp/overlay.png -filter_complex "overlay" -vcodec libx264 -preset ultrafast -profile baseline -pix_fmt yuv420p -an -f flv -rtmp_live live -s 1280x720 rtmp://remote.server.com:1935/show/stream
My Nginx Server config:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
# multi_accept on;
}
http {
##
# Basic Settings
##
# sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;
##
# Logging Settings
##
access_log /var/log/nginx/myccam-access.log;
error_log /var/log/nginx/myccam-error.log;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
# include /etc/nginx/sites-enabled/*;
server {
listen 8080;
location ~ .(mp4|m4a|m4v|mov)$ {
add_header content_disposition filename=$request_uri;
add_header accept_ranges bytes;
}
location /hls {
# Disable cache
add_header Cache-Control no-cache;
# CORS setup
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length';
add_header Cache-Control no-cache;
add_header Last-Modified "";
# allow CORS preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp/;
}
}
}
rtmp {
server {
listen 1935; # Listen on standard RTMP port
chunk_size 4000;
application show {
live on;
hls on;
hls_fragment_naming system;
hls_fragment 1s;
hls_path /tmp/hls;
hls_continuous on;
hls_variant _720p2628kbs BANDWIDTH=2628000;
}
}
}
I proxy this setup through a running Apache server which already has valid SSL certificates. I can access the stream like this via VLC without issue. https://myserver.com/CameraName/hls/stream.m3u8
To view in browser I am using this HTML admittedly borrowed from someone else:
<!DOCTYPE html>
<html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Camera</title>
<style>
html, body {
background: none !important;
overflow: hidden;
margin: 0;
padding: 0;
}
video {
margin: 0 auto;
width: 100%;
height: 100vh;
}
.video-container {
width: 100%;
height: 100vh
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"</script>
<script>
function waitForPublishedStream(showMsg) {
if (typeof showMsg === 'undefined') showMsg = true;
$.get('https://myserver.com/CameraName/hls/stream.m3u8', function (response) {
if (showMsg) {
$('.container-loader .alert-warning').html('Pieslēdzamies kamerai..')
}
if (response.state == 'success') {
if (response.published) {
document.location.reload(true)
} else {
setTimeout(function () {
waitForPublishedStream();
}, 2000)
}
}
})
}
</script>
</head>
<body>
<div class="video-container">
<video id="video" autoplay="autoplay" muted="muted" controls="controls" playsinline="" src="https://myserver.com/CameraName/hls/stream.m3u8">
<source src="https://myserver.com/CameraName/hls/stream.m3u8" type="application/vnd.apple.mpegurl">
</video>
</div>
<script src="https://cdn.jsdelivr.net/npm/hls.js"></script>
<script>
$(document).ready(function () {
const video = document.getElementById('video');
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource('https://myserver.com/CameraName/hls/stream.m3u8');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
}
})
</script>
</body></html>
This works on every device I have tested it on EXCEPT the iPhone. Which is the most important platform for it to work on. On the iPhone the stream just loads forever without displaying anything. The debugger shows that the device is downloading the .ts files correctly. Interestingly the stream will load about 10% of the time if I pop it fullscreen and fast forward the stream. The timestamp on the player shows something way off when this happens. Something like -11:21:42. I suspect this issue may have something to do with the PTS being fed from ffmpeg.
Apples mediastreamvalidator
CLI tool shows that some of the .ts files are being kicked back as 404. But I have not been able to reproduce this on any browser debugger. Any help is greatly appreciated 🙂
References:
2
Answers
I solved this issue by moving away from Nginx entirely and using node-media-server instead
Pretty sure that
Hls.isSupported()
resolves tofalse
for iPhones. It doesn’t for iPads and Macs. The crux is that "HLS.js relies on HTML5 video and MediaSource Extensions for playback." Which is not supported by iPhone Safari.