rustream/templates/
landing.rs

1/// Get the HTML content to render the streaming/landing page.
2///
3/// # See Also
4///
5/// - This page is served as a response for the `/stream` entry point.
6///
7/// # Returns
8///
9/// A `String` version of the HTML, CSS and JS content.
10pub fn get_content() -> String {
11    r###"<!DOCTYPE html>
12<!--suppress JSUnresolvedLibraryURL -->
13<html lang="en">
14<head>
15    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
16    {% if media_title %}
17        <title>{{ media_title }}</title>
18    {% else %}
19        <title>RuStream - Self-hosted Streaming Engine - v{{ version }}</title>
20    {% endif %}
21    <meta property="og:type" content="MediaStreaming">
22    <meta name="keywords" content="Rust, streaming, actix, JavaScript, HTML, CSS">
23    <meta name="author" content="Vignesh Rao">
24    <meta content="width=device-width, initial-scale=1" name="viewport">
25    <!-- CSS and JS for video-js plugin -->
26    <!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
27    <!-- <script src="https://thevickypedia.github.io/open-source/videojs/videojs-ie8.js"></script> -->
28    <link href="https://thevickypedia.github.io/open-source/videojs/video.css" rel="stylesheet"/>
29    <script src="https://thevickypedia.github.io/open-source/videojs/video.js" defer></script>
30    <!-- Favicon.ico and Apple Touch Icon -->
31    <link rel="icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.ico">
32    <link rel="apple-touch-icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.png">
33    <!-- Font Awesome icons -->
34    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/fontawesome.min.css">
35    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/solid.css">
36    <!-- Button CSS -->
37    <style>
38        /* Google fonts with a backup alternative */
39        @import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap');
40        * {
41            font-family: 'Ubuntu', 'PT Serif', sans-serif;
42        }
43        .iter {
44            border: none;
45            padding: 10px 14px;
46            font-size: 16px;
47            cursor: pointer;
48        }
49        .upload {
50            position: absolute;
51            top: 3.8%;
52            right: 313px;
53            border: none;
54            padding: 10px 14px;
55            font-size: 16px;
56            cursor: pointer;
57        }
58        .home {
59            position: absolute;
60            top: 3.8%;
61            right: 217px;
62            border: none;
63            padding: 10px 14px;
64            font-size: 16px;
65            cursor: pointer;
66        }
67        .back {
68            position: absolute;
69            top: 3.8%;
70            right: 132px;
71            border: none;
72            padding: 10px 14px;
73            font-size: 16px;
74            cursor: pointer;
75        }
76        body {
77            background-color: #151515;
78        }
79        title, h1, h2, h3, h4, h5, h6, p, a {
80            color: #f0f0f0;
81        }
82        button {
83            background: transparent !important;
84            color: #f0f0f0;
85        }
86        button:hover {
87            background: transparent !important;
88            opacity: 0.6;
89            transition: 0.5s;
90        }
91    </style>
92    <!-- Container, title and body CSS -->
93    <style>
94        h1 {
95            text-align: center;
96        }
97    </style>
98    <!-- Size of the container and the player -->
99    <style>
100        body {
101            margin: 0; /* Remove default margin */
102            padding: 0; /* Remove default padding */
103            box-sizing: border-box; /* Include padding and border in element's total width and height */
104        }
105        #content-container {
106            position: relative;
107            width: 70%;
108            max-width: 100%; /* Set a maximum width to prevent overflow */
109            height: 75vh; /* Set height to 75% of the viewport height */
110            margin: 0 auto; /* Center the container horizontally */
111        }
112        #nav-container {
113            position: relative;
114            width: 70%;
115            margin: 0 auto; /* Center the container horizontally */
116        }
117        #image-source {
118            max-width: 100%;
119            height: 75vh;
120            margin: 0 auto; /* Center the container horizontally */
121            display: flex;
122            justify-content: center;
123            align-items: center; /* Center the container vertically */
124            cursor: pointer; /* Add a pointer cursor to indicate it's clickable */
125            overflow: hidden; /* Avoid vertical overflow */
126        }
127        #video-player {
128            position: relative;
129            height: 100%;
130            width: 100%;
131            display: block;
132        }
133        @media (max-width: 768px) {
134            #image-source {
135                height: auto;
136                width: 90%;
137            }
138            /* video-js plugin defaults to 120% on mobile phones, so use the same */
139            #content-container {
140                height: auto;
141                width: 120%;
142            }
143            #video-player {
144                height: auto;
145                width: 120%;
146            }
147        }
148    </style>
149    <style>
150        .dropbtn {
151            position: absolute;
152            top: 3.8%;
153            right: 30px;
154            padding: 10px 24px;
155            font-size: 16px;
156            border: none;
157            cursor: pointer;
158        }
159        .dropdown {
160            position: absolute;
161            top: 3.8%;
162            right: 30px;
163            padding: 10px 24px;
164            display: inline-block;
165        }
166        .dropdown-content {
167            display: none;
168            position: absolute;
169            top: 40px;  /* Distance from the user icon button */
170            right: 30px;
171            width: 160px;
172            min-width: auto;
173            box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);  /* Basically, black with 20% opacity */
174            z-index: 1;
175        }
176        .dropdown-content a {
177            padding: 12px 16px;
178            text-decoration: none;
179            display: block;
180        }
181        .dropdown:hover .dropdown-content {display: block;}
182    </style>
183    <noscript>
184        <style>
185            body {
186                width: 100%;
187                height: 100%;
188                overflow: hidden;
189            }
190        </style>
191        <div style="position: fixed; text-align:center; height: 100%; width: 100%; background-color: #151515;">
192            <h2 style="margin-top:5%">This page requires JavaScript
193                to be enabled.
194                <br><br>
195                Please refer <a href="https://www.enable-javascript.com/">enable-javascript</a> for how to.
196            </h2>
197            <form>
198                <button type="submit" onClick="<meta httpEquiv='refresh' content='0'>">RETRY</button>
199            </form>
200        </div>
201    </noscript>
202</head>
203<body>
204    <button class="upload" onclick="upload()"><i class="fa-solid fa-cloud-arrow-up"></i> Upload</button>
205    <button class="home" onclick="goHome()"><i class="fa fa-home"></i> Home</button>
206    <button class="back" onclick="goBack()"><i class="fa fa-backward"></i> Back</button>
207    <div class="dropdown">
208        <button class="dropbtn"><i class="fa fa-user"></i></button>
209        <div class="dropdown-content">
210            <a onclick="goProfile()" style="cursor: pointer;"><i class="fa-solid fa-user-lock"></i> {{ user }}</a>
211            <a onclick="logOut()" style="cursor: pointer"><i class="fa fa-sign-out"></i> logout</a>
212        </div>
213    </div>
214    <br><br><br>
215    <h1>{{ media_title }}</h1>
216    {% if render_image %}
217        <img id="image-source" src="" onclick="fullScreen()">
218    {% else %}
219        <div id="content-container">
220            <video id="video-player"
221                   class="video-js"
222                   preload="auto"
223                   controls muted="muted"
224                   style="position: relative; margin-left: auto; margin-right: auto; display: block"
225                   data-setup='{
226                 "playbackRates": [1, 1.5, 2, 5],
227                 "controlBar": {
228                   "skipButtons": {
229                     "backward": 10,
230                     "forward": 10
231                   }
232                 }
233               }'>
234                <source id="video-source" type="video/mp4" src=""/>
235                <track id="subtitles" kind="subtitles" src="" srclang="en"/>
236                <p class="vjs-no-js">
237                    To view this video please enable JavaScript, and consider upgrading to a
238                    web browser that
239                    <a href="https://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
240                </p>
241            </video>
242        </div>
243    {% endif %}
244    <div id="nav-container">
245        {% if previous %}
246            <button class="iter" style="float: left" onclick="window.location='{{ previous }}'" title="{{ previous }}">
247                <i class="fa fa-backward"></i> Previous
248            </button>
249        {% endif %}
250        {% if next %}
251            <button class="iter" style="float: right" onclick="window.location='{{ next }}'" title="{{ next }}">
252                Next <i class="fa fa-forward"></i>
253            </button>
254        {% endif %}
255        <br><br>
256    </div>
257    <script>
258        let origin = window.location.origin; // Get the current origin using JavaScript
259        let path = "{{ path }}";
260        {% if render_image %}
261            // Construct the source URL for the image by combining origin and path
262            let imageSource = origin + path;
263
264            // Set the image source URL for the image-source element
265            let imageElement = document.getElementById("image-source");
266            imageElement.setAttribute("src", imageSource);
267        {% else %}
268            let track = "{{ track }}";
269
270            // Construct the source URL for video by combining origin and path
271            let videoSource = origin + path;
272
273            // Set the video source URL for the video-source element
274            let videoElement = document.getElementById("video-source");
275            videoElement.setAttribute("src", videoSource);
276
277            // Set the subtitles URL for the video
278            let trackElement = document.getElementById("subtitles");
279            trackElement.setAttribute("src", track);
280
281            let videoPlayer = document.getElementById("video-player");
282            videoPlayer.load(); // Load the video
283            // videoPlayer.play(); // Play the video
284        {% endif %}
285    </script>
286    <script>
287        function goHome() {
288            window.location.href = "/home";
289        }
290        function goProfile() {
291            window.location.href = '/profile';
292        }
293        function logOut() {
294            window.location.href = "/logout";
295        }
296        function upload() {
297            window.location.href = "/upload";
298        }
299        function goBack() {
300            window.history.back();
301        }
302    </script>
303    <script>
304        function fullScreen() {
305            var doc = window.document;
306            // var docEl = doc.documentElement;  // Entire container as fullScreen
307            var docEl = document.getElementById("image-source");  // ONLY image as fullScreen
308            var requestFullScreen =
309                docEl.requestFullscreen ||
310                docEl.mozRequestFullScreen ||
311                docEl.webkitRequestFullScreen ||
312                docEl.msRequestFullscreen;
313            var cancelFullScreen =
314                doc.exitFullscreen ||
315                doc.mozCancelFullScreen ||
316                doc.webkitExitFullscreen ||
317                doc.msExitFullscreen;
318            if (
319                !doc.fullscreenElement &&
320                !doc.mozFullScreenElement &&
321                !doc.webkitFullscreenElement &&
322                !doc.msFullscreenElement
323            ) {
324                if (requestFullScreen === undefined) {
325                    alert("Failed to render {{ media_title }} in fullScreen");
326                }
327                requestFullScreen.call(docEl);
328            } else {
329                if (cancelFullScreen === undefined) {
330                    alert("Failed to cacnel fullScreen for {{ media_title }}");
331                }
332                cancelFullScreen.call(doc);
333            }
334        }
335        {% if previous %}
336            // Add event listener for the left arrow key
337            document.addEventListener('keydown', navigateLeft);
338            function navigateLeft(event) {
339                if (event.key === 'ArrowLeft') {
340                    // Navigate to the previous image
341                    window.location='{{ previous }}';
342                }
343            }
344        {% endif %}
345        {% if next %}
346            // Add event listener for the right arrow key
347            document.addEventListener('keydown', navigateRight);
348            function navigateRight(event) {
349                if (event.key === 'ArrowRight') {
350                    // Navigate to the next image
351                    window.location='{{ next }}';
352                }
353            }
354        {% endif %}
355    </script>
356</body>
357</html>
358"###.to_string()
359}