If you are a fan of this video, support me by buying a fan!: https://amzn.to/2LJPJrb (Amazon Affiliate)
Amcrest Camera Viewing Angle Comparison 104° vs 83°
Amcrest 5MP 104° (IP5M-1173EW): https://amzn.to/3iAK3gU
Amcrest 4MP 118° (IP4M-1026EW): https://amzn.to/2XAJg92
Amcrest 4MP 83° (IP4M-1025E probably discontinued): https://amzn.to/2XzQJVT
Amcrest Playlist: https://m.youtube.com/playlist?list=PLErU2HjQZ_ZNYE224qh9K0hhaaIxU-rta
2×2 Security Camera Matrix in a Web Browser Using FFmpeg on a Raspberry Pi
Creating a 2×2 Security Camera Matrix using FFmpeg: https://youtu.be/Xr1XTl0cAAE
Streaming an IP Camera to a Web Browser using FFmpeg: https://youtu.be/ztjT2YqQ2Hc
Raspberry Pi (Amazon Affiliate)
US: https://amzn.to/2LpyVob
UK: https://amzn.to/2Z2inKX
CA: https://amzn.to/2y5yAUA
ES: https://amzn.to/3fSDhSS
FR: https://amzn.to/2LpurxT
IT: https://amzn.to/2T2VZNu
DE: https://amzn.to/3buHRmQ
IN: https://amzn.to/2B3PGTN
Install nginx and ffmpeg
sudo apt install nginx ffmpeg
Create Index file at /var/www/html/index.html
Be sure to update the ip_address with the ip address of your server.
<!DOCTYPE html> <html lang="en"> <head> <title>Live Cam</title> </head> <body> <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script> <video id="video1" autoplay controls="controls"></video> <script> if (Hls.isSupported()) { var video1 = document.getElementById('video1'); var hls1 = new Hls(); // bind them together hls1.attachMedia(video1); hls1.on(Hls.Events.MEDIA_ATTACHED, function () { console.log("video and hls.js are now bound together !"); hls1.loadSource("http://ip_address/live/stream1/mystream.m3u8"); hls1.on(Hls.Events.MANIFEST_PARSED, function (event, data) { console.log("manifest loaded, found " + data.levels.length + " quality level"); }); }); } </script> <video id="video2" autoplay controls="controls"></video> <script> if (Hls.isSupported()) { var video2 = document.getElementById('video2'); var hls2 = new Hls(); // bind them together hls2.attachMedia(video2); hls2.on(Hls.Events.MEDIA_ATTACHED, function () { console.log("video and hls.js are now bound together !"); hls2.loadSource("http://ip_address/live/stream2/mystream.m3u8"); hls2.on(Hls.Events.MANIFEST_PARSED, function (event, data) { console.log("manifest loaded, found " + data.levels.length + " quality level"); }); }); } </script> <video id="video3" autoplay controls="controls"></video> <script> if (Hls.isSupported()) { var video3 = document.getElementById('video3'); var hls3 = new Hls(); // bind them together hls3.attachMedia(video3); hls3.on(Hls.Events.MEDIA_ATTACHED, function () { console.log("video and hls.js are now bound together !"); hls3.loadSource("http://ip_address/live/stream3/mystream.m3u8"); hls3.on(Hls.Events.MANIFEST_PARSED, function (event, data) { console.log("manifest loaded, found " + data.levels.length + " quality level"); }); }); } </script> <video id="video4" autoplay controls="controls"></video> <script> if (Hls.isSupported()) { var video4 = document.getElementById('video4'); var hls4 = new Hls(); // bind them together hls4.attachMedia(video4); hls4.on(Hls.Events.MEDIA_ATTACHED, function () { console.log("video and hls.js are now bound together !"); hls4.loadSource("http://ip_address/live/stream4/mystream.m3u8"); hls4.on(Hls.Events.MANIFEST_PARSED, function (event, data) { console.log("manifest loaded, found " + data.levels.length + " quality level"); }); }); } </script> </body> </html>
Create Live Stream Directory
sudo mkdir -p /var/www/html/live
Add to fstab to Create 100MB RAM disk
tmpfs /var/www/html/live tmpfs defaults,noatime,nosuid,mode=0755,size=100m 0 0
Create Stream File at /var/www/html/stream1.sh for Each Stream
This is configured for an Amcrest IP Camera but should work with other sources.
#!/bin/bash mkdir -p /var/www/html/live/stream1/ VIDSOURCE="rtsp://username:password@ip_address:554/cam/realmonitor?channel=1&subtype=1" VIDEO_OPTS="-vcodec copy" OUTPUT_HLS="-f hls -hls_time 10 -hls_list_size 10 -hls_flags delete_segments -start_number 1" ffmpeg -nostdin -hide_banner -loglevel panic -i "$VIDSOURCE" -y $VIDEO_OPTS $OUTPUT_HLS /var/www/html/live/stream1/mystream.m3u8
In the /var/www/html Directory, Start Each Stream in the Background
sudo ./stream1.sh & sudo ./stream2.sh & sudo ./stream3.sh & sudo ./stream4.sh &
Stop All Streams
sudo killall ffmpeg
Downloading YouTube Video List for Your Channel (using PHP and Google API)
Create API Key using Google Developer Console
https://console.developers.google.com
Create videolist_download.php File and Paste in this Code
Replace $api_key and $api_playlist_id with your own values.
#!/usr/bin/php <?php // List videos, fifty at a time $api_key = "google_api_key"; $api_playlist_id = "google_playlist_id"; $api_url = "https://www.googleapis.com/youtube/v3/playlistItems?part=snippet%2CcontentDetails&maxResults=50&playlistId=" . $api_playlist_id . "&key=" . $api_key; $pageToken = ''; $pageNumber = 1; // Create directory mkdir("./videolist", 0700, true); // Remove previous files foreach (glob('./videolist/vidlist*.json') as $file) { if(is_file($file)) { unlink($file); } } function downloadVideos($api_url, $pageToken=null) { if ($pageToken != '') { $videos = file_get_contents($api_url . '&pageToken=' . $pageToken); } else { $videos = file_get_contents($api_url); } return $videos; } do { $videos = downloadVideos($api_url, $pageToken); file_put_contents("./videolist/vidlist" . $pageNumber . '.json', $videos); $pageNumber++; $videos_json = json_decode($videos); if (!empty($videos_json->nextPageToken)) { $pageToken = $videos_json->nextPageToken; } else { $pageToken = ''; } } while (!empty($pageToken));
Create videolist_to_html.php File and Paste in this Code
#!/usr/bin/php <?php ob_start(); ?> <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>YouTube videos</title> <style> table {border-collapse: collapse; width: 100%} table td {border: 1px solid black; padding: 12px} .wrap {word-break: break-all;} .nowrap {white-space: nowrap} </style> </head> <table> <?php $file_number = 1; // Convert URLs to Links // https://stackoverflow.com/questions/1960461/convert-plain-text-urls-into-html-hyperlinks-in-php // url regex $url = '~(?:(https?)://([^\s<]+)|(www\.[^\s<]+?\.[^\s<]+))(?<![\.,:])~i'; while ( file_exists("videolist/vidlist" . $file_number . ".json")) { $contents_json = file_get_contents("videolist/vidlist" . $file_number . ".json"); $contents = json_decode($contents_json); foreach ($contents->items as $item) { if ($item->kind == 'youtube#playlistItem') { $rowbuffer = '<tr>'; $rowbuffer .= '<td class="wrap"><strong>'; $rowbuffer .= $item->snippet->title . "</strong><br />"; $description = nl2br($item->snippet->description); $description = preg_replace($url, '<a href="$0" target="_blank" title="$0">$0</a>', $description); $rowbuffer .= $description; $rowbuffer .= '<td class="nowrap">'; $rowbuffer .= substr($item->snippet->publishedAt, 0, 10); $rowbuffer .= '<td class="nowrap">'; $rowbuffer .= '<a href="https://studio.youtube.com/video/' . $item->contentDetails->videoId . '/edit" target="_blank">Edit</a>'; $rowbuffer .= '<td class="nowrap">'; $rowbuffer .= '<a href="https://youtu.be/' . $item->contentDetails->videoId . '" target="_blank">View</a>'; echo $rowbuffer; } } $file_number++; } echo '</table>'; $output_buffer = ob_get_contents(); ob_end_clean(); file_put_contents('videos.html', $output_buffer);
Give Scripts Executable Permissions
chmod +x videolist*.php
Run Scripts
./videolist_download.php ./videolist_to_html.php
Open videos.html in a web browser to view your videos.
Running FFmpeg as a Background Process
FFmpeg Playlist: https://www.youtube.com/playlist?list=PLErU2HjQZ_ZOPDZ71Khzt5PX4X4j6flkg
Sample FFmpeg Command Running as Background Process
ffmpeg -nostdin -hide_banner -nostats -loglevel panic -i input.mp4 -y -vf scale=480:270 output.mp4 &