Implement partial file transfers with response code 206, for video streaming
authorNick Downing <nick@ndcode.org>
Sat, 4 Sep 2021 13:41:24 +0000 (23:41 +1000)
committerNick Downing <nick@ndcode.org>
Sat, 4 Sep 2021 13:41:24 +0000 (23:41 +1000)
Site.js

diff --git a/Site.js b/Site.js
index 0ae12f2..2f16cbd 100644 (file)
--- a/Site.js
+++ b/Site.js
@@ -457,19 +457,59 @@ Site.prototype.serve_min_svg = async function(env, pathname) {
 }
 
 Site.prototype.serve_fs = async function(env, pathname) {
-  // see serve()
-  console.log(
-    `${env.parsed_url.host} streaming ${env.parsed_url.pathname}`
-  )
-
   // see serve_internal()
-  env.response.statusCode = 200
   // since the file may be huge we need to cache it for as long as reasonable
   if (this.options.caching)
     env.response.setHeader('Cache-Control', 'max-age=86400')
   env.response.setHeader('Content-Type', env.mime_type)
 
-  let stream = fs.createReadStream(pathname);
+  // see https://dev.to/abdisalan_js/how-to-code-a-video-streaming-server-using-nodejs-2o0
+  let stream
+  let range = env.request.headers.range;
+  if (range !== undefined) {
+    let stats
+    try {
+      stats = await fs_stat(pathname)
+    }
+    catch (err) {
+      if (!(err instanceof Error) || err.code !== 'ENOENT')
+        throw err
+      return false
+    }
+
+    // Parse Range
+    // Example: "bytes=32324-"
+    let start = Number(range.replace(/\D/g, ''))
+    let end = Math.min(start + 1048576, stats.size)
+
+    // see serve()
+    console.log(
+      `${env.parsed_url.host} streaming ${env.parsed_url.pathname} partial ${start}-${end}/${stats.size}`
+    )
+
+    // Create headers
+    env.response.statusCode = 206 // partial content
+    env.response.setHeader(
+      'Content-Range',
+      `bytes ${start}-${end - 1}/${stats.size}`
+    )
+    env.response.setHeader('Accept-Ranges', 'bytes')
+    env.response.setHeader('Content-Length', end - start)
+    // create video read stream for this particular chunk
+    stream = fs.createReadStream(pathname, {start: start, end: end - 1})
+  }
+  else {
+    // see serve()
+    console.log(
+      `${env.parsed_url.host} streaming ${env.parsed_url.pathname}`
+    )
+
+    // see serve_internal()
+    env.response.statusCode = 200
+    stream = fs.createReadStream(pathname)
+  }
+
   return /*await*/ new Promise(
     (resolve, reject) => {
       stream.on(