WebTorrent integration with WordPress

Distributed audio content (podcasts) generated by users and shared via peer-to-peer (P2P) architectures and protocols can lower the entry barrier for community based radios that at the same time are wary of the privacy implications of the free offerings by commercial centralised platforms.

Although not a P2P architecture itself, a website is a useful way to disseminate the torrent podcasts, publishing the magnet-links and making these contents easily and transparently available to users that may not be familiar with torrents and P2P, including in-browser streaming.

WebTorrent is a neat streaming torrent client for the web browser (and the desktop). It can play media content shared via P2P, and automatically re-share (seeding) while listening or viewing it. The user only needs to know the podcast identification on the P2P network (magnet link or hash code).

An easy integration of WebTorrent with the popular WordPress content management system would be useful. An existing old plugin has not been updated in three years and currently does not work with WP [last version].

Instead of developing a new dedicated WebTorrent plugin, I achieved a similar function through a custom shortcode, a handy way to extend WP capabilities that can be added with the existing Shortcoder plugin.

After installing the Shortcoder plugin, it is sufficient to define the new shortcode with the following code (based on examples found on the WebTorrent site), and naming it e.g. “webtorrent”:

<style>
      #output%%id:1%% video {
        width: 100%;
      }
      #progressBar%%id:1%% {
          height: 5px;
          width: 0%;
          // background-color: #35b44f;
          transition: width .4s ease-in-out;
      }
      body.is-seed .show-seed {
          display: inline;
      }
      body.is-seed .show-leech {
          display: none;
      }
      .show-seed {
          display: none;
      }
      #status%%id:1%% code {
          font-size: 90%;
          font-weight: 700;
          margin-left: 3px;
          margin-right: 3px;
          border-bottom: 1px dashed rgba(255,255,255,0.3);
      }

      .is-seed #hero%%id:1%% {
          // background-color: #154820;
          transition: .5s .5s background-color ease-in-out;
      }
      #hero%%id:1%% {
          // background-color: #2a3749;
      }
      #status%%id:1%% {
          // color: #fff;
          font-size: 17px;
          padding: 5px;
      }
</style>

<!-- Include the latest version of WebTorrent -->
<script src="https://cdn.jsdelivr.net/npm/webtorrent@latest/webtorrent.min.js"></script>

<!-- Moment is used to show a human-readable remaining time -->
<script src="https://momentjs.com/downloads/moment.js"></script>


<div id="hero%%id:1%%">
     <div id="output%%id:1%%">
        <div id="progressBar%%id:1%%"></div>
        <!-- The video player will be added here -->
     </div>


   <!-- Statistics -->
   <div id="status%%id:1%%">
        <div>
          <span class="show-leech">Downloading </span>
          <span class="show-seed">Seeding </span>
          
                <!-- Informative link to the torrent file -->
                <a id="torrentLink%%id:1%%" href="%%magnetlink%%">$$enclosed_content$$</a>
              
          <span class="show-leech"> from </span>
          <span class="show-seed"> to </span>
          0 peers.
        </div>
        <div>
          
          of 
          — <span id="remaining%%id:1%%"></span><br/>
          &#x2198;0 b/s
          / &#x2197;0 b/s
    </div>
  </div>
</div>

<script>
      var torrentId%%id:1%% = '%%magnetlink%%'

      var client = new WebTorrent()

      // HTML elements
      var $body%%id:1%% = document.body
      var $progressBar%%id:1%% = document.querySelector('#progressBar%%id:1%%')
      var $numPeers%%id:1%% = document.querySelector('#numPeers%%id:1%%')
      var $downloaded%%id:1%% = document.querySelector('#downloaded%%id:1%%')
      var $total%%id:1%% = document.querySelector('#total%%id:1%%')
      var $remaining%%id:1%% = document.querySelector('#remaining%%id:1%%')
      var $uploadSpeed%%id:1%% = document.querySelector('#uploadSpeed%%id:1%%')
      var $downloadSpeed%%id:1%% = document.querySelector('#downloadSpeed%%id:1%%')

      // Download the torrent
      client.add(torrentId%%id:1%%, function (torrent) {

        // Torrents can contain many files. Let's use the .mp4 file for videos or .m4a for audio
        var file%%id:1%% = torrent.files.find(function (file) {
          return file.name.endsWith('.%%extension:m4a%%')
        })

        // Stream the file in the browser
        file%%id:1%%.appendTo('#output%%id:1%%')

        // Trigger statistics refresh
        torrent.on('done', onDone)
        setInterval(onProgress, 500)
        onProgress()

        // Statistics
        function onProgress () {
          // Peers
          $numPeers%%id:1%%.innerHTML = torrent.numPeers + (torrent.numPeers === 1 ? ' peer' : ' peers')

          // Progress
          var percent%%id:1%% = Math.round(torrent.progress * 100 * 100) / 100
          $progressBar%%id:1%%.style.width = percent%%id:1%% + '%'
          $downloaded%%id:1%%.innerHTML = prettyBytes(torrent.downloaded)
          $total%%id:1%%.innerHTML = prettyBytes(torrent.length)

          // Remaining time
          var remaining%%id:1%%
          if (torrent.done) {
            remaining%%id:1%% = 'Done.'
          } else {
            remaining%%id:1%% = moment.duration(torrent.timeRemaining / 1000, 'seconds').humanize()
            remaining%%id:1%% = remaining%%id:1%%[0].toUpperCase() + remaining%%id:1%%.substring(1) + ' remaining.'
          }
          $remaining%%id:1%%.innerHTML = remaining%%id:1%%

          // Speed rates
          $downloadSpeed%%id:1%%.innerHTML = prettyBytes(torrent.downloadSpeed) + '/s'
          $uploadSpeed%%id:1%%.innerHTML = prettyBytes(torrent.uploadSpeed) + '/s'
        }
        function onDone () {
          $body%%id:1%%.className += ' is-seed'
          onProgress()
        }
      })

      // Human readable bytes util
      function prettyBytes(num) {
        var exponent, unit, neg = num < 0, units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
        if (neg) num = -num
        if (num < 1) return (neg ? '-' : '') + num + ' B'
        exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), units.length - 1)
        num = Number((num / Math.pow(1000, exponent)).toFixed(2))
        unit = units[exponent]
        return (neg ? '-' : '') + num + ' ' + unit
      }
</script>

The shortcode downloads the WebTorrent client, written in javascript, from its CDN, as well as moment.js used to show a human-readable remaining time while downloading. If this function is not needed, moment.js download can be omitted.

The parameters %%id:1 and the like allow to embed more than one WebTorrent podcast in each post/page.

Typical usage within a WordPress post or page:

[sc name="webtorrent" id="1" magnetlink="magnet:?..." extension="m4a"]Text to be shown in the post (linked to the podcast)[/sc]

Where:

name: is the name of the shortcake created in the previous step;

id: is a progressive numeral (default=“1”) to be manually increased if there are more than one podcast embedded in the post/page;

magnetlink: is self-explanatory;

extension: extension of the media filename — audio or video — contained in the magnet-link that we want to stream and play; supported: mp3, m4a, mp4, ogg, etc. (check WebTorrent docs for details); defaults to: “m4a”

The shortcode contains some stylesheet directives that the user can easily customise, as well as torrent statistics about the number of peers, upload/download speed, etc. that can be easily commented out if not needed.