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.