Reading time: 2 minutes
PUBLISH DATE: Sep 13 2019
UPD: Jul 04 2023
Reading time: 2 minutes
Tech
Do you want to develop your custom solution?
Book a call
Phone

Keenethics Experience-Sharing: How to Create a Simple Torrent Player?

Have you ever heard about PopcornTime? This app allows you to play a video from BitTorrent without downloading the whole file. Why not develop a similar thing but for audio? It is a good opportunity to practice and to learn some Node.js basics.

Here, I will show only how to work with mp3, but you may easily reimplement other formats using libraries. As a source tracker, I will use rutracker. I have tried to use PirateBay, but it does not have a way to get tracks path without taking torrent info, which is useful to play a single track faster.

I use Electron since it is simpler, but you can easily take it to usual Node.js + simple static server. I have three main js scripts: rutracker search wrapper, client code, and server-side code. The code is not perfect, and it is very simplified for POC.

Rutracker.js: Rutracker Search Wrapper

const torrentStream = require(‘torrent-stream’); const RutrackerAPI = require(‘rutracker-api-2’); const pRequest = require(‘request-promise’); const cheerio = require(‘cheerio’); const rutracker = new RutrackerAPI(); let trackerCookie; const RUTRACKER_LOGIN = “searchertrack”, RUTRACKER_PASSWORD = “1q1Q1q1Q”; (async ()=>{ trackerCookie = await rutracker.login(RUTRACKER_LOGIN, RUTRACKER_PASSWORD); })(); const getTrackerMP3List = (html, magnetHTML) => { const $ = cheerio.load(html); const $m = cheerio.load(magnetHTML); const elem = $(‘.ftree’); const magnet = $m(‘[class*=”magnet-link-“]’).attr(‘href’) const result = Array.from($elem.find(‘b:contains(.mp3)’)).map((x, i) => { const title = $(x).text(); const $dir = $(x).parents(‘.dir’); const pathParts = Array.from($dir).map(x => $(x).find(‘>div b’).text().replace(‘./’, ”)).reverse(); pathParts.push(title); const resultPath = pathParts.join(‘ || ‘); return { id: ${Date.now()}${i}, trackPath: resultPath, shortname: title, title: resultPath, isTracker: true, magnet, }; }); return result; }; module.exports = { TrackerFiles: async (url) => { const id = url.replace(‘http://rutracker.org/forum/viewtopic.php?t=’, ”); const magnetHTML = await pRequest(url, { headers: { cookie: trackerCookie, }, }); const html = await pRequest(‘https://rutracker.org/forum/viewtorrent.php’, { method: “POST”, headers: { cookie: trackerCookie, }, form: { t: id } }); const result = getTrackerMP3List(html, magnetHTML); return result; }, Search: async (str) => { const data = await rutracker.search(str, ‘size’, false); return { playlist: data .filter(x => x.title.toLowerCase().indexOf(‘mp3’) > -1) .map(x => ({ permalink_url: x.url, title: x.title || “NO_TITLE”, id: x.id, type: ‘tracker’, })), user: [], track: [] }; } };

Serv.js: Server-Side Code

const torrentStream = require(‘torrent-stream’); const fastify = require(‘fastify’)(); const util = require(‘util’); module.exports = (port) => { let engine, track; fastify.get(‘/trackerStream’, async (request, response) => { const { trackPath, magnet } = request.query; const filePathTorr = trackPath.split(‘ || ‘).join(‘\’); const filePathTorrLinux = trackPath.split(‘ || ‘).join(‘/’); if (engine) engine.destroy(); engine = torrentStream(magnet); engine.on(‘error’, (err) => console.log(err)); await util.promisify(engine.on(‘ready’)); track = engine.files.find(x => x.path === filePathTorr || x.path === filePathTorrLinux); const total = track.length; const range = request.headers.range; if (range) { const parts = range.replace(/bytes=/, “”).split(“-“); const partialstart = parts[0]; const partialend = parts[1]; const start = parseInt(partialstart, 10); const end = partialend ? parseInt(partialend, 10) : total – 1; const chunksize = (end – start) + 1; response .code(206) .header(‘Content-Range’, ‘bytes ‘ + start + ‘-‘ + end + ‘/’ + total) .header(‘Accept-Ranges’, ‘bytes’) .header(‘Content-Length’, chunksize) .header(‘Content-Type’, ‘audio/mpeg’) .send(track .createReadStream({ start, end }) .on(‘end’, () => { console.log(‘Downloaded’); engine.destroy(); })); } else { response .header(‘Content-Type’, ‘audio/mpeg’) .send(track.createReadStream()); } }) const start = async () => { try { await fastify.listen(port); console.log(server listening on ${fastify.server.address().port}); } catch (err) { console.error(err); } } start() } }

I use fastify as a server framework. It exposes only one route, which is used for streaming torrent to audio tag. At the same time, it has 2 limitations.

Firstly, web-torrent does not support TCP/UDP in browser, while Node.js development does. Respectively, I can only use torrents that have seeders based on WebRTC fallback. In case I want to get magnet url from tracker and just play i, it will not work.

Secondly, I just choose a simpler way to pipe Node.js readable Stream to browser audio. I have found an alternative way, but it does not seem to support track seeking.

Let’s see what the code does.

I select a track on the client side and pass trackPath argument to the server. It contains the full path to a specific track including the directory structure.

Then, I get torrent info by magnet, find a specific file inside, take read stream, and pipe it to response. This url is used as file inside audio tag src. A good thing is that createReadStream supports ranges, so it is easy to implement audio seeking.

Main.js: Client Code

const RuTracker = require(‘./js/rutracker.js’); const fs = require(‘fs’); const port = 3007; require(‘./js/serv.js’)(port); document .querySelector(‘#searchSubmit’) .addEventListener(‘click’, async () => { const searchStr = document.querySelector(‘#searchStr’).value.toLowerCase(); try { const result = await RuTracker.Search(searchStr); const items = result .playlist .map(x => <div class="searchItem" onClick="listTracks('${x.permalink_url}')">${x.title}</div>); document.querySelector(‘#searchOutput’).innerHTML = items.join(”); } catch (ex) { console.error(ex); } }); const listTracks = async (url) => { const tracks = await RuTracker.TrackerFiles(url); const items = tracks.map(x => <div class="searchItem" onClick="playTrack(\${x.trackPath}`, ‘${x.magnet}’)”>${x.title}); document.querySelector('#searchOutput').innerHTML = items.join(''); console.log(tracks); } const playTrack = async (trackPath, magnet) => { document.querySelector('#audioOutput').src =http://localhost:${port}/trackerStream?trackPath=${trackPath}&magnet=${encodeURIComponent(magnet)}`; }

This file can be used for a simple search and audio output.

To Wrap Up

I have provided an example of how easy it can be to create a torrent player of some kind. Sure, it has some issues, such as the search sometimes does not work or huge files are opened slowly. Yet, it is just a POC, and most of these problems can be solved.

You can find code here: https://github.com/demogoran/ElectroPlay

Do you have an idea for a project?

If there is something you need assistance with, we will be happy to share our experience or to help you implement the idea!

Rate this article!
5/5
Reviews: 2
You have already done it before!
Do you want to develop your custom solution?
Book a call
Phone
Start growing your business with us

Get ready to meet your next proactive tech partner. Tell us about your project, and we'll contact you within one business day, providing an action plan

Only for communication
By submitting, I agree to Keenethics’ Privacy Policy.
Daria Hlavcheva
Daria Hlavcheva
Head of Partner Engagement
Book a call
What to expect after submitting the form?
  • Our Engagement Manager will reply within 1 business day.
  • You'll receive an optional NDA to sign.
  • We'll schedule a call to discuss the action plan.

Our Projects

We've helped to develop multiple high-quality projects. Learn more about them in our case study section

BankerAdvisor - Investment Banking Tool
  • Business
  • Finance & Banking

Find the best investment banking option.

Case studies
Attendance
  • Business administration

Tracking schedules and salaries of the Keenethics team

Case studies
Brainable
  • Business
  • E-commerce
  • Education
  • Entertainment

A brain-training website helping you discover what your mind can do.

Case studies
StoryTerrace Bookmaker
  • Business
  • E-commerce
  • Education
  • Entertainment

Book publishing platform helping you create your own book online with a competent in-house editorial team.

Case studies
Check out our case studies
Case Studies
GDPR banner icon
We use cookies to analyze traffic and make your experience on our website better. More about our Cookie Policy and GDPR Privacy Policy