PUBLISH DATE: Sep 13 2019
UPD: Jul 29 2025
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 Your Conversation With Us
Choose what you are looking for
Attach file
By submitting, I agree to Keenethics’ Privacy Policy
Bring your ideas straight to the call
With global availability, our booking options overlap with business hours from the US West Coast to Europe and Australia
Book a call
  • Initial Discussion – share your vision and expectations with the team
  • Requirements Clarification – align priorities, scope, and success criteria
  • Proposal – get comprehensive cooperation plan with budgets and timelines

Our Projects

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

Ruuster
  • Real Estate

Ruuster is a user-friendly tool designed to simplify the home-buying process for clients, helping them secure the home of their dreams.

Case studies
Scentarium
  • Education

Scentarium is a Swiss web platform for aromatherapy enthusiasts that offers a comprehensive database of essential oils, hydrosols, carrier oils, and various recipes for combining them.

Case studies
Bizlinga
  • Education

Bizlinga is a mobile-based learning app designed to teach business communication in an interactive and motivating way, following engagement mechanisms similar to Duolingo. 

Case studies
ADS
  • Manufacturing

ADS is a leading Ukrainian manufacturer of aluminum decorum systems, which prides itself on providing a wide range of high-quality products and a flexible customization service that allows creating unique furniture.

Case studies
Mayak
  • Non-Profit

Mayak is an online information platform for guiding the Lviv, Ukraine residents in exploring available mental health services in their home city.

Case studies
SelfLeaders
  • Education

SelfLeaders is a Swedish coaching company that helps its clients (both corporate and non-corporate) improve their self-leadership skills.

Case studies
Offerin
  • Real Estate

Offerin aims to optimize offer management processes for real estate agents in Ohio, US, taking the most minute details of local legislation into consideration.

Case studies
Altruous
  • Non-Profit

Altruous is a next-generation platform for discovering, evaluating, and managing various programs that want to deliver a positive social impact. It is built by a team of mission-driven leaders transforming the future of philanthropy.

Case studies
Winery Management — Optimizing Wine Storage and Sales Processes
  • Finance & eCommerce

Winery Management is a platform for optimizing wine storage and sales processes, helping its users track wine inventory, create wine lists, and manage sales transactions.

Case studies
BrightSpace — A Dashboard for Intelligent Building Management
  • Real Estate

BrightSpace is an AI-based building management platform that optimizes HVAC systems, offering its users a dashboard for analyzing and managing real-time occupancy, air quality, and energy consumption in large commercial spaces.

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