Android app now is working, still some problems with series sound but movies are fine. and video works.
This commit is contained in:
510
README.md
510
README.md
@@ -1,204 +1,444 @@
|
||||
# beStream
|
||||
<p align="center">
|
||||
<img src="public/icon.svg" alt="beStream Logo" width="120" height="120">
|
||||
</p>
|
||||
|
||||
A cross-platform movie streaming application with a Netflix-style UI, built with React and capable of running on Android, iOS, Windows, Linux, and macOS.
|
||||
<h1 align="center">beStream</h1>
|
||||
|
||||
## Features
|
||||
<p align="center">
|
||||
<strong>A cross-platform streaming application with a Netflix-style interface</strong>
|
||||
</p>
|
||||
|
||||
- 🎬 **Netflix-style UI** - Beautiful, responsive interface with hero banners, horizontal scrolling rows, and smooth animations
|
||||
- 🔍 **Browse & Search** - Filter movies by genre, quality, rating, and more with infinite scroll
|
||||
- 📥 **Real Torrent Streaming** - Stream movies via backend server with WebTorrent + FFmpeg transcoding
|
||||
- 🎥 **HLS Playback** - Adaptive streaming with HLS.js for smooth playback
|
||||
- 💾 **Offline Downloads** - Download movies for offline viewing
|
||||
- 📝 **Watchlist** - Save movies to watch later
|
||||
- 📊 **Watch History** - Track viewing progress and resume playback
|
||||
- 🌐 **Multi-Platform** - Single codebase for all platforms
|
||||
<p align="center">
|
||||
<a href="#features">Features</a> •
|
||||
<a href="#platforms">Platforms</a> •
|
||||
<a href="#installation">Installation</a> •
|
||||
<a href="#development">Development</a> •
|
||||
<a href="#building">Building</a> •
|
||||
<a href="#documentation">Documentation</a>
|
||||
</p>
|
||||
|
||||
## Architecture
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/version-2.4.0-blue.svg" alt="Version">
|
||||
<img src="https://img.shields.io/badge/react-18.3-61dafb.svg" alt="React">
|
||||
<img src="https://img.shields.io/badge/electron-33.2-47848f.svg" alt="Electron">
|
||||
<img src="https://img.shields.io/badge/capacitor-7.4-119eff.svg" alt="Capacitor">
|
||||
<img src="https://img.shields.io/badge/license-private-red.svg" alt="License">
|
||||
</p>
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────────────────────────┐
|
||||
│ React App │────▶│ Node.js Backend │
|
||||
│ (Frontend) │ │ │
|
||||
│ Port: 5173 │◀────│ WebTorrent ──▶ FFmpeg ──▶ HLS │
|
||||
└─────────────────┘ │ Port: 3001 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
## ✨ Features
|
||||
|
||||
| Component | Technology |
|
||||
|-----------|------------|
|
||||
| UI Framework | React 18 + TypeScript |
|
||||
| Build Tool | Vite |
|
||||
| Styling | TailwindCSS + Framer Motion |
|
||||
| State Management | Zustand |
|
||||
| Mobile | Capacitor (iOS/Android) |
|
||||
| Desktop | Electron (Windows/Linux/macOS) |
|
||||
| Torrent | WebTorrent |
|
||||
| Storage | IndexedDB (Dexie.js) |
|
||||
- 🎬 **Stream Movies & TV Shows** - Real-time streaming via WebTorrent
|
||||
- 🎨 **Netflix-Style UI** - Beautiful, responsive dark theme interface
|
||||
- 📱 **Cross-Platform** - Windows, macOS, Linux, and Android support
|
||||
- 🔍 **Smart Search** - Search across multiple content providers
|
||||
- 📺 **TV Show Support** - Browse seasons, episodes with auto-play
|
||||
- 📥 **Download Manager** - Queue and manage downloads
|
||||
- 📚 **Watch History** - Track your progress across devices
|
||||
- ⭐ **Watchlist** - Save content for later
|
||||
- 🎯 **Quality Selection** - Choose from 480p to 4K
|
||||
- 🔗 **Integrations** - Connect with Radarr, Sonarr, and Lidarr
|
||||
- 🌐 **Subtitle Support** - Load external subtitles (SRT)
|
||||
|
||||
## Getting Started
|
||||
---
|
||||
|
||||
## 🖥️ Platforms
|
||||
|
||||
| Platform | Status | Technology |
|
||||
|----------|--------|------------|
|
||||
| Windows | ✅ Supported | Electron |
|
||||
| macOS | ✅ Supported | Electron |
|
||||
| Linux | ✅ Supported | Electron |
|
||||
| Android | ✅ Supported | Capacitor + NodeJS |
|
||||
| iOS | 🚧 Planned | Capacitor |
|
||||
| Web | 🔧 Dev Only | Vite |
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js 18+
|
||||
- npm or yarn
|
||||
- For iOS: Xcode 14+ (macOS only)
|
||||
- For Android: Android Studio with SDK 24+
|
||||
- For Desktop: No additional requirements
|
||||
- **Node.js** 18.x or higher
|
||||
- **npm** 9.x or higher
|
||||
- **Git**
|
||||
|
||||
### Prerequisites
|
||||
For Android builds:
|
||||
- **Android Studio** with SDK 33+
|
||||
- **Java JDK** 17+
|
||||
|
||||
- Node.js 18+
|
||||
- **FFmpeg** - Required for video transcoding
|
||||
- Windows: `winget install FFmpeg` or download from https://ffmpeg.org
|
||||
- macOS: `brew install ffmpeg`
|
||||
- Linux: `sudo apt install ffmpeg`
|
||||
|
||||
### Installation
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
git clone https://github.com/yourusername/bestream.git
|
||||
cd bestream
|
||||
|
||||
# Install frontend dependencies
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Install server dependencies
|
||||
npm run server:install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Start both frontend and streaming server
|
||||
# Start development server
|
||||
npm run dev:all
|
||||
|
||||
# Or start them separately:
|
||||
npm run dev # Frontend only (http://localhost:5173)
|
||||
npm run server # Backend only (http://localhost:3001)
|
||||
|
||||
# Start Electron development
|
||||
npm run electron:dev
|
||||
```
|
||||
|
||||
### How Streaming Works
|
||||
The app will be available at `http://localhost:5173` with the backend server at `http://localhost:3001`.
|
||||
|
||||
1. Click "Play" on any movie
|
||||
2. Backend downloads torrent via WebTorrent
|
||||
3. FFmpeg transcodes to HLS format in real-time
|
||||
4. Frontend plays HLS stream with adaptive quality
|
||||
5. Watch immediately while download continues in background
|
||||
---
|
||||
|
||||
### Building
|
||||
## 🛠️ Development
|
||||
|
||||
### Available Scripts
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm run dev` | Start Vite dev server (frontend only) |
|
||||
| `npm run dev:all` | Start frontend + backend server |
|
||||
| `npm run server` | Start backend server only |
|
||||
| `npm run build` | Build frontend for production |
|
||||
| `npm run lint` | Run ESLint |
|
||||
| `npm run electron:dev` | Start Electron in dev mode |
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
beStream/
|
||||
├── src/ # React frontend
|
||||
│ ├── components/ # UI components
|
||||
│ │ ├── layout/ # Header, Sidebar, Layout
|
||||
│ │ ├── movie/ # Hero, MovieRow, MovieCard
|
||||
│ │ ├── tv/ # TV show components
|
||||
│ │ ├── player/ # Video player components
|
||||
│ │ └── ui/ # Button, Modal, Skeleton, etc.
|
||||
│ ├── pages/ # Route pages
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── services/ # API and business logic
|
||||
│ │ ├── api/ # YTS, EZTV, TMDB clients
|
||||
│ │ ├── streaming/ # Streaming service
|
||||
│ │ └── server/ # Server management
|
||||
│ ├── stores/ # Zustand state stores
|
||||
│ ├── types/ # TypeScript definitions
|
||||
│ └── utils/ # Utility functions
|
||||
├── server/ # Node.js backend
|
||||
│ └── src/
|
||||
│ ├── routes/ # Express routes
|
||||
│ │ ├── stream.js # Streaming endpoints
|
||||
│ │ └── proxy.js # API proxy endpoints
|
||||
│ └── services/ # Backend services
|
||||
│ ├── torrentManager.js # WebTorrent client
|
||||
│ └── transcoder.js # FFmpeg transcoding
|
||||
├── electron/ # Electron main process
|
||||
│ ├── main.ts # Main process entry
|
||||
│ └── preload.ts # Preload scripts
|
||||
├── android/ # Android native project
|
||||
├── scripts/ # Build scripts
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
### Tech Stack
|
||||
|
||||
**Frontend:**
|
||||
- React 18 with TypeScript
|
||||
- Vite for bundling
|
||||
- Tailwind CSS for styling
|
||||
- Zustand for state management
|
||||
- Framer Motion for animations
|
||||
- React Router for navigation
|
||||
- HLS.js for adaptive streaming
|
||||
|
||||
**Backend:**
|
||||
- Express.js web server
|
||||
- WebTorrent for P2P streaming
|
||||
- FFmpeg for HLS transcoding (desktop only)
|
||||
- WebSocket for real-time updates
|
||||
|
||||
**Desktop:**
|
||||
- Electron 33
|
||||
- Custom frameless window
|
||||
- System tray integration
|
||||
|
||||
**Mobile:**
|
||||
- Capacitor 7
|
||||
- capacitor-nodejs plugin
|
||||
- Embedded Node.js runtime
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Building
|
||||
|
||||
### Desktop (Electron)
|
||||
|
||||
```bash
|
||||
# Build web version
|
||||
npm run build
|
||||
|
||||
# Build for Android
|
||||
npm run build:android
|
||||
|
||||
# Build for iOS
|
||||
npm run build:ios
|
||||
|
||||
# Build for Windows
|
||||
# Windows
|
||||
npm run build:win
|
||||
|
||||
# Build for macOS
|
||||
# macOS
|
||||
npm run build:mac
|
||||
|
||||
# Build for Linux
|
||||
# Linux
|
||||
npm run build:linux
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
**Output:** `release/` directory
|
||||
|
||||
```
|
||||
bestream/
|
||||
├── src/
|
||||
│ ├── components/ # React components
|
||||
│ │ ├── ui/ # Base UI components
|
||||
│ │ ├── movie/ # Movie-related components
|
||||
│ │ ├── player/ # Video player components
|
||||
│ │ └── layout/ # Layout components
|
||||
│ ├── pages/ # Route pages
|
||||
│ ├── services/ # API and business logic
|
||||
│ │ ├── api/ # YTS API client
|
||||
│ │ ├── torrent/ # WebTorrent service
|
||||
│ │ ├── subtitles/ # OpenSubtitles service
|
||||
│ │ └── storage/ # IndexedDB service
|
||||
│ ├── stores/ # Zustand state stores
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── utils/ # Helper utilities
|
||||
│ └── types/ # TypeScript definitions
|
||||
├── electron/ # Electron main process
|
||||
├── public/ # Static assets
|
||||
└── docs/ # Documentation
|
||||
Built files:
|
||||
- Windows: `beStream Setup x.x.x.exe`
|
||||
- macOS: `beStream-x.x.x.dmg`
|
||||
- Linux: `beStream-x.x.x.AppImage`
|
||||
|
||||
### Android
|
||||
|
||||
```bash
|
||||
# Debug APK
|
||||
npm run build:android:apk
|
||||
|
||||
# Release APK (signed)
|
||||
npm run build:android:release
|
||||
```
|
||||
|
||||
## API Reference
|
||||
**Output:** `android/app/build/outputs/apk/`
|
||||
|
||||
The app uses the YTS API for movie data:
|
||||
### Build Process (Android)
|
||||
|
||||
- **List Movies**: `GET /list_movies.json`
|
||||
- **Movie Details**: `GET /movie_details.json`
|
||||
- **Movie Suggestions**: `GET /movie_suggestions.json`
|
||||
- **Parental Guides**: `GET /movie_parental_guides.json`
|
||||
The Android build process involves several steps:
|
||||
|
||||
See the [YTS API Documentation](https://yts.mx/api) for more details.
|
||||
1. **Build Frontend** (`npm run build`)
|
||||
- TypeScript compilation
|
||||
- Vite production build
|
||||
- Output: `dist/`
|
||||
|
||||
## Platform-Specific Notes
|
||||
2. **Bundle Server** (`npm run copy-server-to-android`)
|
||||
- Bundle with esbuild into single file
|
||||
- Apply platform polyfills
|
||||
- Output: `dist/nodejs-v3/index.js`
|
||||
|
||||
### Mobile (iOS/Android)
|
||||
3. **Sync Capacitor** (`npx cap sync android`)
|
||||
- Copy web assets to Android
|
||||
- Update native plugins
|
||||
|
||||
- Torrent streaming uses WebRTC-based WebTorrent
|
||||
- Downloads are stored in app-specific storage
|
||||
- Supports picture-in-picture playback
|
||||
4. **Build APK** (`gradlew assembleDebug`)
|
||||
- Compile Android project
|
||||
- Package native + web resources
|
||||
|
||||
### Desktop (Windows/Linux/macOS)
|
||||
---
|
||||
|
||||
- Full WebTorrent support with DHT
|
||||
- Downloads can be saved to any location
|
||||
- System tray integration
|
||||
- Native menu bar
|
||||
## 📚 Documentation
|
||||
|
||||
## Configuration
|
||||
Detailed documentation is available in the `docs/` directory:
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [DOCUMENTATION.md](docs/DOCUMENTATION.md) | Full technical documentation |
|
||||
| [GETTING_API_KEYS.md](docs/GETTING_API_KEYS.md) | Setup external API keys |
|
||||
| [OPENING_PORTS.md](docs/OPENING_PORTS.md) | Network configuration guide |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Create a `.env` file in the root directory:
|
||||
|
||||
```env
|
||||
# Optional: OpenSubtitles API key for subtitle support
|
||||
VITE_OPENSUBTITLES_API_KEY=your-api-key
|
||||
# API Configuration (optional)
|
||||
VITE_API_URL=http://localhost:3001
|
||||
|
||||
# EZTV Authentication (optional)
|
||||
VITE_EZTV_USERNAME=your_username
|
||||
VITE_EZTV_PASSWORD=your_password
|
||||
```
|
||||
|
||||
### Capacitor Configuration
|
||||
### App Settings
|
||||
|
||||
Edit `capacitor.config.ts` to customize mobile settings.
|
||||
Settings are stored locally and persist across sessions:
|
||||
|
||||
### Electron Configuration
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| Preferred Quality | 1080p | Default streaming quality |
|
||||
| Language | en | Preferred content language |
|
||||
| Auto Play | true | Auto-play next episode |
|
||||
| Theme | dark | UI theme (dark/light) |
|
||||
| Max Downloads | 3 | Concurrent download limit |
|
||||
|
||||
Edit `electron/main.ts` to customize desktop settings.
|
||||
### Integration Settings
|
||||
|
||||
## Legal Notice
|
||||
Connect to media managers for enhanced features:
|
||||
|
||||
This application is provided for educational purposes only. It does not host any content and simply provides an interface to the YTS API. Users are responsible for complying with local laws regarding content streaming and downloading.
|
||||
- **Radarr** - Movie collection management
|
||||
- **Sonarr** - TV show collection management
|
||||
- **Lidarr** - Music collection management
|
||||
|
||||
## License
|
||||
Configure in Settings → Integrations.
|
||||
|
||||
MIT License - See LICENSE file for details.
|
||||
---
|
||||
|
||||
## Contributing
|
||||
## 🔌 API Endpoints
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Submit a pull request
|
||||
### Backend Server (Port 3001)
|
||||
|
||||
## Support
|
||||
| Endpoint | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `/health` | GET | Server health check |
|
||||
| `/api/stream/start` | POST | Start streaming session |
|
||||
| `/api/stream/:id/status` | GET | Get session status |
|
||||
| `/api/stream/:id/video` | GET | Stream video |
|
||||
| `/api/stream/:id/hls` | POST | Start HLS transcoding |
|
||||
| `/api/eztv/*` | GET | EZTV API proxy |
|
||||
| `/api/yts/*` | GET | YTS API proxy |
|
||||
| `/api/tmdb/*` | GET | TMDB API proxy |
|
||||
|
||||
For issues and feature requests, please use the GitHub issue tracker.
|
||||
### WebSocket (Port 3001/ws)
|
||||
|
||||
Real-time streaming progress updates:
|
||||
|
||||
```javascript
|
||||
// Connect
|
||||
const ws = new WebSocket('ws://localhost:3001/ws');
|
||||
|
||||
// Subscribe to session
|
||||
ws.send(JSON.stringify({ type: 'subscribe', sessionId: 'xxx' }));
|
||||
|
||||
// Receive updates
|
||||
ws.onmessage = (e) => {
|
||||
const { type, progress, downloadSpeed, peers } = JSON.parse(e.data);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Android Notes
|
||||
|
||||
### Requirements
|
||||
|
||||
- Android 7.0 (API 24) or higher
|
||||
- ~100MB storage space
|
||||
- Internet connection
|
||||
|
||||
### Limitations
|
||||
|
||||
Due to platform constraints, the Android version has some limitations:
|
||||
|
||||
| Feature | Desktop | Android |
|
||||
|---------|---------|---------|
|
||||
| HLS Transcoding | ✅ | ❌ |
|
||||
| uTP Protocol | ✅ | ❌ |
|
||||
| File Downloads | ✅ | Limited |
|
||||
| FFmpeg Processing | ✅ | ❌ |
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# View app logs
|
||||
adb logcat | grep com.bestream.app
|
||||
|
||||
# View Node.js server logs
|
||||
adb logcat | grep -E "(NodeJS|beStream Server)"
|
||||
|
||||
# Install debug build
|
||||
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
# Uninstall and reinstall
|
||||
adb uninstall com.bestream.app
|
||||
adb install android/app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Server not starting:**
|
||||
```bash
|
||||
# Check if port is in use
|
||||
netstat -an | grep 3001
|
||||
|
||||
# Kill process on port
|
||||
npx kill-port 3001
|
||||
```
|
||||
|
||||
**Android build fails:**
|
||||
```bash
|
||||
# Clean and rebuild
|
||||
cd android
|
||||
./gradlew clean
|
||||
cd ..
|
||||
npm run build:android:apk
|
||||
```
|
||||
|
||||
**Video not playing:**
|
||||
- Check server health: `curl http://localhost:3001/health`
|
||||
- Verify torrent has seeders
|
||||
- Try different quality option
|
||||
- Check browser console for errors
|
||||
|
||||
**CORS errors in development:**
|
||||
- Ensure Vite proxy is configured
|
||||
- Check that backend is running on port 3001
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions! Please follow these steps:
|
||||
|
||||
1. **Fork** the repository
|
||||
2. **Create** a feature branch: `git checkout -b feature/amazing-feature`
|
||||
3. **Commit** changes: `git commit -m 'Add amazing feature'`
|
||||
4. **Push** to branch: `git push origin feature/amazing-feature`
|
||||
5. **Open** a Pull Request
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Write TypeScript for all new code
|
||||
- Follow existing code style (ESLint + Prettier)
|
||||
- Use functional components with hooks
|
||||
- Add tests for new features
|
||||
- Update documentation as needed
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
|
||||
This application is intended for **educational and personal use only**. Users are solely responsible for ensuring they have the legal right to stream or download any content. The developers:
|
||||
|
||||
- Do not host any content
|
||||
- Do not provide torrents or links to copyrighted material
|
||||
- Are not responsible for user actions
|
||||
- Encourage compliance with local laws and regulations
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is **private and proprietary**. Unauthorized copying, modification, distribution, or use of this software is strictly prohibited without explicit permission.
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
Special thanks to these amazing projects:
|
||||
|
||||
- [WebTorrent](https://webtorrent.io) - Streaming torrent library
|
||||
- [Electron](https://electronjs.org) - Desktop application framework
|
||||
- [Capacitor](https://capacitorjs.com) - Cross-platform native runtime
|
||||
- [React](https://reactjs.org) - UI library
|
||||
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS
|
||||
- [YTS](https://yts.mx) - Movie data provider
|
||||
- [EZTV](https://eztv.re) - TV show data provider
|
||||
- [TMDB](https://themoviedb.org) - Movie and TV metadata
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<sub>Built with ❤️ by the beStream Team</sub>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<sub>Version 2.4.0 • Last Updated: January 2026</sub>
|
||||
</p>
|
||||
|
||||
|
||||
758
docs/DOCUMENTATION.md
Normal file
758
docs/DOCUMENTATION.md
Normal file
@@ -0,0 +1,758 @@
|
||||
# beStream Technical Documentation
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Architecture](#architecture)
|
||||
3. [Project Structure](#project-structure)
|
||||
4. [Frontend Application](#frontend-application)
|
||||
5. [Backend Server](#backend-server)
|
||||
6. [Platform Support](#platform-support)
|
||||
7. [API Reference](#api-reference)
|
||||
8. [State Management](#state-management)
|
||||
9. [Services](#services)
|
||||
10. [Build System](#build-system)
|
||||
11. [Configuration](#configuration)
|
||||
12. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
beStream is a cross-platform movie and TV show streaming application with a Netflix-style UI. It supports:
|
||||
|
||||
- **Desktop**: Windows, macOS, Linux (via Electron)
|
||||
- **Mobile**: Android (via Capacitor with embedded Node.js)
|
||||
- **Web**: Browser-based development mode
|
||||
|
||||
The application streams content via WebTorrent, providing real-time torrent-to-stream functionality with optional HLS transcoding for broader device compatibility.
|
||||
|
||||
### Key Features
|
||||
|
||||
- 🎬 Stream movies and TV shows in real-time
|
||||
- 📺 Netflix-style responsive UI
|
||||
- 🔍 Search and browse content from YTS and EZTV
|
||||
- 📱 Cross-platform support (Desktop & Android)
|
||||
- ⬇️ Download management with progress tracking
|
||||
- 📚 Watch history and watchlist
|
||||
- 🎯 Quality selection (480p, 720p, 1080p, 2160p)
|
||||
- 🔄 Integration with Radarr/Sonarr/Lidarr
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (React) │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
|
||||
│ │ Pages │ │Components│ │ Stores │ │ Services │ │
|
||||
│ │ │ │ │ │ (Zustand)│ │ (API, Streaming) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │
|
||||
└──────────────────────────────┬──────────────────────────────────┘
|
||||
│ HTTP/WebSocket
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Backend Server (Node.js) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Express │ │ WebTorrent │ │ FFmpeg Transcoder │ │
|
||||
│ │ Routes │ │ Manager │ │ (HLS streaming) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Platform-Specific Architecture
|
||||
|
||||
#### Electron (Desktop)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Electron App │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ Main Process │ │
|
||||
│ │ - Window management │ │
|
||||
│ │ - IPC handling │ │
|
||||
│ │ - Server process spawn │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ Renderer Process │ │
|
||||
│ │ - React Frontend │ │
|
||||
│ │ - Preload scripts │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ Server Process (fork) │ │
|
||||
│ │ - Express + WebTorrent │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### Capacitor (Android)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Android Application │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ WebView │ │
|
||||
│ │ - React Frontend │ │
|
||||
│ │ - Capacitor Bridge │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
│ ┌─────────────────────────────────┐ │
|
||||
│ │ capacitor-nodejs plugin │ │
|
||||
│ │ - Embedded Node.js runtime │ │
|
||||
│ │ - Express + WebTorrent bundle │ │
|
||||
│ └─────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
beStream/
|
||||
├── src/ # Frontend React application
|
||||
│ ├── components/ # Reusable UI components
|
||||
│ │ ├── calendar/ # Calendar-related components
|
||||
│ │ ├── integration/ # *arr integration components
|
||||
│ │ ├── layout/ # Layout components (Header, Sidebar)
|
||||
│ │ ├── movie/ # Movie display components
|
||||
│ │ ├── music/ # Music-related components
|
||||
│ │ ├── player/ # Video player components
|
||||
│ │ ├── tv/ # TV show components
|
||||
│ │ └── ui/ # Generic UI components (Button, Modal, etc.)
|
||||
│ ├── pages/ # Page components (routes)
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── services/ # API and business logic services
|
||||
│ │ ├── api/ # External API clients (YTS, EZTV, TMDB)
|
||||
│ │ ├── integration/ # *arr service integrations
|
||||
│ │ ├── server/ # Server management
|
||||
│ │ ├── storage/ # Local storage services
|
||||
│ │ ├── streaming/ # Streaming service client
|
||||
│ │ ├── subtitles/ # Subtitle handling
|
||||
│ │ └── torrent/ # Torrent utilities
|
||||
│ ├── stores/ # Zustand state stores
|
||||
│ ├── types/ # TypeScript type definitions
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── constants/ # Application constants
|
||||
├── server/ # Backend Node.js server
|
||||
│ ├── src/
|
||||
│ │ ├── routes/ # Express route handlers
|
||||
│ │ ├── services/ # Backend services (torrent, transcoder)
|
||||
│ │ ├── utils/ # Server utilities
|
||||
│ │ └── polyfills/ # Platform polyfills
|
||||
│ ├── bundle.js # esbuild bundler script
|
||||
│ └── package.json # Server dependencies
|
||||
├── electron/ # Electron main process
|
||||
│ ├── main.ts # Main process entry
|
||||
│ ├── preload.ts # Preload script
|
||||
│ └── tsconfig.json # Electron TypeScript config
|
||||
├── android/ # Android native project (Capacitor)
|
||||
├── scripts/ # Build and utility scripts
|
||||
├── docs/ # Documentation
|
||||
├── public/ # Static assets
|
||||
├── dist/ # Built frontend
|
||||
├── dist-electron/ # Built Electron files
|
||||
└── release/ # Release builds
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Application
|
||||
|
||||
### Technology Stack
|
||||
|
||||
- **React 18** - UI framework
|
||||
- **TypeScript** - Type safety
|
||||
- **Vite** - Build tool and dev server
|
||||
- **Tailwind CSS** - Utility-first styling
|
||||
- **Zustand** - State management
|
||||
- **React Router** - Client-side routing
|
||||
- **Framer Motion** - Animations
|
||||
- **Axios** - HTTP client
|
||||
- **HLS.js** - HLS video playback
|
||||
|
||||
### Pages
|
||||
|
||||
| Page | Route | Description |
|
||||
|------|-------|-------------|
|
||||
| Home | `/` | Hero section with trending content |
|
||||
| Browse | `/browse`, `/browse/:genre` | Browse movies by genre |
|
||||
| Search | `/search` | Search movies and TV shows |
|
||||
| Movie Details | `/movie/:id` | Movie information and stream options |
|
||||
| Player | `/player/:id` | Video player with streaming |
|
||||
| TV Shows | `/tv` | Browse TV shows |
|
||||
| TV Show Details | `/tv/:id` | Season/episode browser |
|
||||
| TV Player | `/tv/:showId/:seasonId/:episodeId` | TV episode player |
|
||||
| Watchlist | `/watchlist` | Saved items |
|
||||
| Downloads | `/downloads` | Download manager |
|
||||
| History | `/history` | Watch history |
|
||||
| Queue | `/queue` | Download queue |
|
||||
| Settings | `/settings` | App configuration |
|
||||
|
||||
### Component Architecture
|
||||
|
||||
#### Layout Components
|
||||
|
||||
- **Layout** - Main app layout with header and sidebar
|
||||
- **Header** - Top navigation with search
|
||||
- **Sidebar** - Navigation menu
|
||||
|
||||
#### Movie Components
|
||||
|
||||
- **Hero** - Featured content carousel
|
||||
- **MovieRow** - Horizontal scrollable movie list
|
||||
- **MovieCard** - Individual movie thumbnail
|
||||
- **MovieGrid** - Grid layout for movies
|
||||
|
||||
#### Player Components
|
||||
|
||||
- **StreamingPlayer** - Main video player wrapper
|
||||
- **VideoPlayer** - Core video element with controls
|
||||
- **NextEpisodeOverlay** - Auto-play next episode UI
|
||||
|
||||
### Hooks
|
||||
|
||||
| Hook | Purpose |
|
||||
|------|---------|
|
||||
| `useMovies` | Fetch and cache movie data |
|
||||
| `useSeries` | Fetch TV show data |
|
||||
| `useCalendar` | Episode calendar data |
|
||||
| `useIntegration` | *arr service integration |
|
||||
| `useAsyncData` | Generic async data fetching |
|
||||
| `useMusic` | Music service data |
|
||||
|
||||
---
|
||||
|
||||
## Backend Server
|
||||
|
||||
### Technology Stack
|
||||
|
||||
- **Express.js** - Web framework
|
||||
- **WebTorrent** - Torrent client
|
||||
- **FFmpeg** (optional) - Video transcoding
|
||||
- **ws** - WebSocket server
|
||||
- **mime-types** - MIME type detection
|
||||
|
||||
### Server Entry Point
|
||||
|
||||
The server initializes in `server/src/index.js`:
|
||||
|
||||
1. Creates Express app with security headers
|
||||
2. Sets up CORS for cross-origin requests
|
||||
3. Registers API routes
|
||||
4. Creates WebSocket server for real-time updates
|
||||
5. Binds to `localhost:3001` (or `0.0.0.0:3001` on Android)
|
||||
|
||||
### Routes
|
||||
|
||||
#### Stream Routes (`/api/stream`)
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/start` | Start a new streaming session |
|
||||
| GET | `/:sessionId/status` | Get session status |
|
||||
| GET | `/:sessionId/video` | Stream video (supports range requests) |
|
||||
| POST | `/:sessionId/hls` | Start HLS transcoding |
|
||||
| GET | `/:sessionId/hls/playlist.m3u8` | Get HLS playlist |
|
||||
| GET | `/:sessionId/hls/:segment` | Get HLS segment |
|
||||
| DELETE | `/:sessionId` | End streaming session |
|
||||
|
||||
#### Proxy Routes (`/api`)
|
||||
|
||||
| Endpoint | Target | Purpose |
|
||||
|----------|--------|---------|
|
||||
| `/api/eztv/*` | eztvx.to | TV show torrents |
|
||||
| `/api/yts/*` | yts.mx | Movie torrents |
|
||||
| `/api/tmdb/*` | themoviedb.org | Movie/TV metadata |
|
||||
|
||||
### Services
|
||||
|
||||
#### TorrentManager
|
||||
|
||||
Manages WebTorrent client and streaming sessions:
|
||||
|
||||
```javascript
|
||||
class TorrentManager {
|
||||
// Start a new torrent session
|
||||
async startSession(sessionId, torrentHash, movieName, onProgress)
|
||||
|
||||
// Create readable stream for video file
|
||||
createReadStream(sessionId, options)
|
||||
|
||||
// Get session status (progress, speed, peers)
|
||||
getSessionStatus(sessionId)
|
||||
|
||||
// Clean up session
|
||||
destroySession(sessionId)
|
||||
}
|
||||
```
|
||||
|
||||
Key features:
|
||||
- Session reuse for duplicate torrents
|
||||
- Range request support for seeking
|
||||
- Progress tracking via callbacks
|
||||
- Platform-aware cache directories
|
||||
|
||||
#### Transcoder
|
||||
|
||||
Handles FFmpeg-based HLS transcoding (desktop only):
|
||||
|
||||
```javascript
|
||||
class Transcoder {
|
||||
// Get video file information
|
||||
getVideoInfo(inputPath)
|
||||
|
||||
// Start HLS transcoding from file
|
||||
startHlsTranscode(sessionId, inputPath, onProgress, quality)
|
||||
|
||||
// Start HLS transcoding from stream
|
||||
startHlsStreamTranscode(sessionId, inputStream, onProgress, quality)
|
||||
|
||||
// Clean up transcoding session
|
||||
cleanup(sessionId)
|
||||
}
|
||||
```
|
||||
|
||||
Note: FFmpeg transcoding is disabled on Android due to binary unavailability.
|
||||
|
||||
---
|
||||
|
||||
## Platform Support
|
||||
|
||||
### Electron (Desktop)
|
||||
|
||||
The Electron build provides:
|
||||
- Native window with frameless design (macOS) or custom frame (Windows/Linux)
|
||||
- System tray integration
|
||||
- File system access for downloads
|
||||
- Embedded server process
|
||||
|
||||
#### Main Process (`electron/main.ts`)
|
||||
|
||||
- Window creation and management
|
||||
- Server process lifecycle
|
||||
- IPC handlers for native features
|
||||
- Menu and tray setup
|
||||
|
||||
#### Preload Script (`electron/preload.ts`)
|
||||
|
||||
Exposes safe APIs to renderer:
|
||||
- `window.electron.openExternal(url)` - Open URLs in browser
|
||||
- `window.electron.getDownloadsPath()` - Get downloads directory
|
||||
- `window.electron.showItemInFolder(path)` - Show file in explorer
|
||||
|
||||
### Capacitor (Android)
|
||||
|
||||
The Android build uses:
|
||||
- Capacitor WebView for UI
|
||||
- `capacitor-nodejs` plugin for embedded Node.js
|
||||
- Bundled server (single `index.js` file)
|
||||
|
||||
#### Build Process
|
||||
|
||||
1. Build frontend with Vite
|
||||
2. Bundle server with esbuild
|
||||
3. Copy bundle to Android assets
|
||||
4. Sync Capacitor
|
||||
5. Build APK with Gradle
|
||||
|
||||
#### Platform Considerations
|
||||
|
||||
- **No FFmpeg**: Transcoding disabled, direct video streaming only
|
||||
- **No uTP**: WebTorrent uTP disabled (native module incompatible)
|
||||
- **Cache Directory**: Uses `/data/data/com.bestream.app/files/` instead of `/tmp`
|
||||
- **Network**: Server binds to `0.0.0.0` for WebView access
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
|
||||
### External APIs
|
||||
|
||||
#### YTS API
|
||||
|
||||
Movie data and torrents from YTS.
|
||||
|
||||
```typescript
|
||||
// List movies with filters
|
||||
ytsApi.listMovies({
|
||||
limit: 20,
|
||||
page: 1,
|
||||
quality: '1080p',
|
||||
genre: 'action',
|
||||
sort_by: 'rating',
|
||||
order_by: 'desc',
|
||||
query_term: 'search term'
|
||||
})
|
||||
|
||||
// Get movie details
|
||||
ytsApi.getMovieDetails({ movie_id: 12345 })
|
||||
|
||||
// Get suggestions
|
||||
ytsApi.getMovieSuggestions(12345)
|
||||
```
|
||||
|
||||
#### EZTV API
|
||||
|
||||
TV show torrents from EZTV.
|
||||
|
||||
```typescript
|
||||
// Get torrents for a show (by IMDB ID)
|
||||
tvDiscoveryApi.getTorrents('tt1234567')
|
||||
|
||||
// Search episode torrents
|
||||
tvDiscoveryApi.searchEpisodeTorrents(showTitle, season, episode, imdbId)
|
||||
```
|
||||
|
||||
#### TMDB API
|
||||
|
||||
TV show metadata from The Movie Database.
|
||||
|
||||
```typescript
|
||||
// Get trending shows
|
||||
tvDiscoveryApi.getTrending()
|
||||
|
||||
// Get show details
|
||||
tvDiscoveryApi.getShowDetails(showId)
|
||||
|
||||
// Get season details
|
||||
tvDiscoveryApi.getSeasonDetails(showId, seasonNumber)
|
||||
```
|
||||
|
||||
### Internal APIs
|
||||
|
||||
#### Streaming Service
|
||||
|
||||
```typescript
|
||||
// Start a stream
|
||||
streamingService.startStream(hash, name, quality)
|
||||
|
||||
// Connect to WebSocket updates
|
||||
streamingService.connect(sessionId)
|
||||
|
||||
// Subscribe to progress updates
|
||||
streamingService.subscribe(sessionId, callback)
|
||||
|
||||
// Get video URL
|
||||
streamingService.getVideoUrl(sessionId)
|
||||
|
||||
// Check server health
|
||||
streamingService.checkHealth()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## State Management
|
||||
|
||||
The app uses Zustand for state management with persistence.
|
||||
|
||||
### Stores
|
||||
|
||||
#### Settings Store
|
||||
|
||||
```typescript
|
||||
interface SettingsState {
|
||||
settings: {
|
||||
preferredQuality: '480p' | '720p' | '1080p' | '2160p';
|
||||
preferredLanguage: string;
|
||||
autoPlay: boolean;
|
||||
notifications: boolean;
|
||||
downloadPath: string;
|
||||
maxConcurrentDownloads: number;
|
||||
theme: 'dark' | 'light';
|
||||
};
|
||||
updateSettings(settings: Partial<AppSettings>): void;
|
||||
resetSettings(): void;
|
||||
}
|
||||
```
|
||||
|
||||
#### History Store
|
||||
|
||||
```typescript
|
||||
interface HistoryState {
|
||||
history: HistoryItem[];
|
||||
addToHistory(item: HistoryItem): void;
|
||||
updateProgress(id: number, progress: number): void;
|
||||
getProgress(id: number): HistoryItem | undefined;
|
||||
removeFromHistory(id: number): void;
|
||||
clearHistory(): void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Watchlist Store
|
||||
|
||||
```typescript
|
||||
interface WatchlistState {
|
||||
watchlist: WatchlistItem[];
|
||||
addToWatchlist(item: WatchlistItem): void;
|
||||
removeFromWatchlist(id: number): void;
|
||||
isInWatchlist(id: number): boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### Download Store
|
||||
|
||||
```typescript
|
||||
interface DownloadState {
|
||||
downloads: Download[];
|
||||
addDownload(download: Download): void;
|
||||
updateDownload(id: string, updates: Partial<Download>): void;
|
||||
removeDownload(id: string): void;
|
||||
}
|
||||
```
|
||||
|
||||
#### Integration Store
|
||||
|
||||
```typescript
|
||||
interface IntegrationState {
|
||||
radarrUrl: string;
|
||||
radarrApiKey: string;
|
||||
sonarrUrl: string;
|
||||
sonarrApiKey: string;
|
||||
lidarrUrl: string;
|
||||
lidarrApiKey: string;
|
||||
// ... setters
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Services
|
||||
|
||||
### API Services (`src/services/api/`)
|
||||
|
||||
- **yts.ts** - YTS API client for movies
|
||||
- **tvDiscovery.ts** - TMDB + EZTV for TV shows
|
||||
- **radarr.ts** - Radarr integration
|
||||
- **sonarr.ts** - Sonarr integration
|
||||
- **lidarr.ts** - Lidarr integration
|
||||
|
||||
### Streaming Services (`src/services/streaming/`)
|
||||
|
||||
- **streamingService.ts** - Main streaming client
|
||||
- HTTP requests to backend
|
||||
- WebSocket connection management
|
||||
- Session lifecycle
|
||||
|
||||
### Server Services (`src/services/server/`)
|
||||
|
||||
- **serverManager.ts** - Server lifecycle management
|
||||
- Start/stop server (Capacitor)
|
||||
- Health checks
|
||||
- Automatic restarts
|
||||
|
||||
### Storage Services (`src/services/storage/`)
|
||||
|
||||
- Local database (Dexie/IndexedDB)
|
||||
- Download management
|
||||
- Offline data caching
|
||||
|
||||
---
|
||||
|
||||
## Build System
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Frontend only
|
||||
npm run dev
|
||||
|
||||
# Frontend + Server
|
||||
npm run dev:all
|
||||
|
||||
# Electron development
|
||||
npm run electron:dev
|
||||
```
|
||||
|
||||
### Production Builds
|
||||
|
||||
```bash
|
||||
# Web build
|
||||
npm run build
|
||||
|
||||
# Electron builds
|
||||
npm run build:win # Windows
|
||||
npm run build:mac # macOS
|
||||
npm run build:linux # Linux
|
||||
|
||||
# Android build
|
||||
npm run build:android:apk # Debug APK
|
||||
npm run build:android:release # Release APK
|
||||
```
|
||||
|
||||
### Build Pipeline (Android)
|
||||
|
||||
1. **Frontend Build** (`npm run build`)
|
||||
- TypeScript compilation
|
||||
- Vite production build
|
||||
- Output: `dist/`
|
||||
|
||||
2. **Server Install** (`npm run server:install`)
|
||||
- Install server dependencies
|
||||
|
||||
3. **Server Bundle** (`npm run copy-server-to-android`)
|
||||
- Bundle with esbuild
|
||||
- Single file output
|
||||
- Platform-aware polyfills
|
||||
- Output: `dist/nodejs-v3/index.js`
|
||||
|
||||
4. **Capacitor Sync** (`npx cap sync android`)
|
||||
- Copy web assets
|
||||
- Update native project
|
||||
|
||||
5. **Gradle Build** (`gradlew assembleDebug`)
|
||||
- Compile Android project
|
||||
- Output: `android/app/build/outputs/apk/`
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `VITE_API_URL` | Backend server URL | `http://localhost:3001` |
|
||||
| `PORT` | Server port | `3001` |
|
||||
| `NODE_ENV` | Environment mode | `development` |
|
||||
| `VITE_EZTV_USERNAME` | EZTV authentication | - |
|
||||
| `VITE_EZTV_PASSWORD` | EZTV authentication | - |
|
||||
|
||||
### Capacitor Config (`capacitor.config.ts`)
|
||||
|
||||
```typescript
|
||||
{
|
||||
appId: 'com.bestream.app',
|
||||
appName: 'beStream',
|
||||
webDir: 'dist',
|
||||
plugins: {
|
||||
CapacitorNodeJS: {
|
||||
nodeDir: 'nodejs-v3',
|
||||
startMode: 'manual',
|
||||
},
|
||||
},
|
||||
android: {
|
||||
allowMixedContent: true,
|
||||
cleartext: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Vite Config (`vite.config.ts`)
|
||||
|
||||
- React plugin
|
||||
- Path aliases (`@/` → `src/`)
|
||||
- Development proxy for APIs
|
||||
- Rollup externals for Capacitor
|
||||
|
||||
### Tailwind Config (`tailwind.config.js`)
|
||||
|
||||
Custom theme with Netflix-inspired colors:
|
||||
- `netflix-black`: `#141414`
|
||||
- `netflix-red`: `#E50914`
|
||||
- Custom spacing and breakpoints
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Server Not Starting (Android)
|
||||
|
||||
**Symptoms**: App shows "Server not running" error
|
||||
|
||||
**Solutions**:
|
||||
1. Check Logcat for Node.js errors
|
||||
2. Verify `nodejs-v3/index.js` exists in APK
|
||||
3. Ensure `builtin_modules` placeholder exists
|
||||
4. Check for native module errors (node-datachannel, uTP)
|
||||
|
||||
#### Video Not Playing
|
||||
|
||||
**Symptoms**: Black screen or loading forever
|
||||
|
||||
**Solutions**:
|
||||
1. Check server health: `http://localhost:3001/health`
|
||||
2. Verify torrent has seeders
|
||||
3. Check browser console for errors
|
||||
4. Try different quality/torrent
|
||||
|
||||
#### CORS Errors
|
||||
|
||||
**Symptoms**: Network errors in browser console
|
||||
|
||||
**Solutions**:
|
||||
1. Ensure server CORS is configured
|
||||
2. Check proxy configuration in Vite
|
||||
3. Verify API URLs are correct
|
||||
|
||||
#### Build Failures (Android)
|
||||
|
||||
**Symptoms**: Gradle build errors
|
||||
|
||||
**Solutions**:
|
||||
1. Clean build: `cd android && gradlew clean`
|
||||
2. Sync Capacitor: `npx cap sync android`
|
||||
3. Check Java/Gradle versions
|
||||
4. Verify Android SDK installation
|
||||
|
||||
### Debug Tools
|
||||
|
||||
#### Logcat (Android)
|
||||
|
||||
```bash
|
||||
# All app logs
|
||||
adb logcat | grep com.bestream.app
|
||||
|
||||
# Node.js logs only
|
||||
adb logcat | grep -E "(NodeJS|beStream)"
|
||||
```
|
||||
|
||||
#### Server Health Check
|
||||
|
||||
```bash
|
||||
curl http://localhost:3001/health
|
||||
```
|
||||
|
||||
#### WebSocket Testing
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:3001/ws');
|
||||
ws.onopen = () => ws.send(JSON.stringify({ type: 'subscribe', sessionId: 'test' }));
|
||||
ws.onmessage = (e) => console.log(JSON.parse(e.data));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
### Code Style
|
||||
|
||||
- TypeScript for all new code
|
||||
- ESLint + Prettier for formatting
|
||||
- Functional components with hooks
|
||||
- Named exports preferred
|
||||
|
||||
### Testing
|
||||
|
||||
- Unit tests for utilities
|
||||
- Integration tests for services
|
||||
- E2E tests for critical flows
|
||||
|
||||
### Pull Request Process
|
||||
|
||||
1. Create feature branch
|
||||
2. Make changes with tests
|
||||
3. Update documentation
|
||||
4. Submit PR with description
|
||||
5. Pass CI checks
|
||||
6. Get review approval
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is private and proprietary. See LICENSE file for details.
|
||||
@@ -56,8 +56,9 @@ function copyServerToAndroid() {
|
||||
writeFileSync(join(distNodejsDir, 'package.json'), JSON.stringify(pkg, null, 2));
|
||||
}
|
||||
|
||||
// 6. Handle builtin_modules (Capacitor requirement)
|
||||
const pluginBuiltinModules = join(rootDir, 'node_modules', 'capacitor-nodejs', 'android', 'src', 'main', 'assets', 'builtin_modules');
|
||||
// 6. Handle builtin_modules (Capacitor NodeJS requirement)
|
||||
// The plugin expects a builtin_modules folder in assets, but the source only has .gitkeep
|
||||
// which Android's AssetManager ignores. We must create a real file.
|
||||
const androidBuiltinModules = join(rootDir, 'android', 'app', 'src', 'main', 'assets', 'builtin_modules');
|
||||
|
||||
if (!existsSync(dirname(androidBuiltinModules))) {
|
||||
@@ -68,25 +69,14 @@ function copyServerToAndroid() {
|
||||
rmSync(androidBuiltinModules, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (existsSync(pluginBuiltinModules)) {
|
||||
console.log('Copying builtin_modules...');
|
||||
// Simple recursive copy for builtin_modules
|
||||
const copyRecursive = (src, dest) => {
|
||||
const stats = statSync(src);
|
||||
if (stats.isDirectory()) {
|
||||
mkdirSync(dest, { recursive: true });
|
||||
readdirSync(src).forEach(child => {
|
||||
copyRecursive(join(src, child), join(dest, child));
|
||||
});
|
||||
} else {
|
||||
copyFileSync(src, dest);
|
||||
}
|
||||
};
|
||||
copyRecursive(pluginBuiltinModules, androidBuiltinModules);
|
||||
} else {
|
||||
mkdirSync(androidBuiltinModules, { recursive: true });
|
||||
writeFileSync(join(androidBuiltinModules, 'README.txt'), 'Placeholder for builtin_modules');
|
||||
}
|
||||
// Always create the directory with a proper placeholder file
|
||||
// Android's AssetManager ignores .gitkeep and empty directories
|
||||
console.log('Creating builtin_modules placeholder...');
|
||||
mkdirSync(androidBuiltinModules, { recursive: true });
|
||||
writeFileSync(
|
||||
join(androidBuiltinModules, 'README.txt'),
|
||||
'Placeholder for capacitor-nodejs builtin_modules directory.\nThis file ensures Android AssetManager recognizes this folder.'
|
||||
);
|
||||
|
||||
console.log('✓ Server bundled and copied successfully to android/assets/nodejs-v3');
|
||||
console.log(' NOTE: node_modules were explicitely SKIPPED because we are using a bundle.');
|
||||
|
||||
@@ -23,6 +23,10 @@ try {
|
||||
'fsevents', // macOS only
|
||||
'electron', // Not available in this environment
|
||||
],
|
||||
alias: {
|
||||
// Provide stub for native WebRTC module on Android (native .node binaries don't work)
|
||||
'node-datachannel': resolve(__dirname, 'src/polyfills/node-datachannel-stub.js'),
|
||||
},
|
||||
loader: {
|
||||
'.node': 'file', // Handle .node files if any
|
||||
},
|
||||
@@ -37,12 +41,12 @@ try {
|
||||
format: 'esm',
|
||||
banner: {
|
||||
js: `
|
||||
import { createRequire } from 'module';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
const require = createRequire(import.meta.url);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
import { createRequire as _createRequire } from 'module';
|
||||
import { fileURLToPath as _fileURLToPath } from 'url';
|
||||
import { dirname as _dirname } from 'path';
|
||||
const require = _createRequire(import.meta.url);
|
||||
const __filename = _fileURLToPath(import.meta.url);
|
||||
const __dirname = _dirname(__filename);
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
// Early startup log for debugging
|
||||
console.log('[beStream Server] Starting initialization...');
|
||||
console.log('[beStream Server] Platform:', process.platform);
|
||||
console.log('[beStream Server] Node version:', process.version);
|
||||
console.log('[beStream Server] CWD:', process.cwd());
|
||||
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import { WebSocketServer } from 'ws';
|
||||
import { createServer } from 'http';
|
||||
import { streamRouter } from './routes/stream.js';
|
||||
import { proxyRouter } from './routes/proxy.js';
|
||||
import { torrentManager } from './services/torrentManager.js';
|
||||
|
||||
console.log('[beStream Server] Imports loaded successfully');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
console.log('[beStream Server] Will listen on port:', PORT);
|
||||
|
||||
// Security headers middleware
|
||||
app.use((req, res, next) => {
|
||||
@@ -43,6 +53,7 @@ app.get('/health', (req, res) => {
|
||||
|
||||
// API Routes
|
||||
app.use('/api/stream', streamRouter);
|
||||
app.use('/api', proxyRouter); // Proxy routes for EZTV, YTS, TMDB
|
||||
|
||||
// Create HTTP server
|
||||
const server = createServer(app);
|
||||
@@ -94,7 +105,10 @@ process.on('SIGTERM', async () => {
|
||||
// On other platforms, localhost is fine
|
||||
const HOST = process.platform === 'android' ? '0.0.0.0' : 'localhost';
|
||||
|
||||
console.log('[beStream Server] Attempting to bind to', HOST + ':' + PORT);
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log('[beStream Server] SUCCESS! Server is now listening');
|
||||
console.log(`
|
||||
╔═══════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
@@ -108,3 +122,8 @@ server.listen(PORT, HOST, () => {
|
||||
`);
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.error('[beStream Server] ERROR binding to port:', err.message);
|
||||
console.error('[beStream Server] Full error:', err);
|
||||
});
|
||||
|
||||
|
||||
58
server/src/polyfills/node-datachannel-stub.js
Normal file
58
server/src/polyfills/node-datachannel-stub.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Stub for node-datachannel native module
|
||||
* This module cannot work on Android because it requires compiled native binaries.
|
||||
* WebTorrent uses this for WebRTC peer connections, but on mobile we can fall back
|
||||
* to websocket-based trackers which don't require WebRTC.
|
||||
*/
|
||||
|
||||
class PeerConnection {
|
||||
constructor() {
|
||||
console.warn('[node-datachannel-stub] PeerConnection is not available on this platform');
|
||||
}
|
||||
|
||||
createDataChannel() {
|
||||
throw new Error('WebRTC DataChannel is not available on this platform');
|
||||
}
|
||||
|
||||
setLocalDescription() {}
|
||||
setRemoteDescription() {}
|
||||
addRemoteCandidate() {}
|
||||
close() {}
|
||||
|
||||
onLocalDescription() {}
|
||||
onLocalCandidate() {}
|
||||
onStateChange() {}
|
||||
onGatheringStateChange() {}
|
||||
onDataChannel() {}
|
||||
}
|
||||
|
||||
class DataChannel {
|
||||
constructor() {
|
||||
console.warn('[node-datachannel-stub] DataChannel is not available on this platform');
|
||||
}
|
||||
|
||||
send() {}
|
||||
close() {}
|
||||
|
||||
onOpen() {}
|
||||
onClosed() {}
|
||||
onError() {}
|
||||
onMessage() {}
|
||||
}
|
||||
|
||||
// Export stubs
|
||||
export { PeerConnection, DataChannel };
|
||||
export const RtcpReceivingSession = class {};
|
||||
export const Track = class {};
|
||||
export const Video = class {};
|
||||
export const Audio = class {};
|
||||
|
||||
// Default export for CommonJS compatibility
|
||||
export default {
|
||||
PeerConnection,
|
||||
DataChannel,
|
||||
RtcpReceivingSession,
|
||||
Track,
|
||||
Video,
|
||||
Audio,
|
||||
};
|
||||
145
server/src/routes/proxy.js
Normal file
145
server/src/routes/proxy.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import express from 'express';
|
||||
import https from 'https';
|
||||
import http from 'http';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Helper function to make HTTP/HTTPS requests
|
||||
function makeRequest(targetUrl, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const url = new URL(targetUrl);
|
||||
const isHttps = url.protocol === 'https:';
|
||||
const client = isHttps ? https : http;
|
||||
|
||||
const requestOptions = {
|
||||
hostname: url.hostname,
|
||||
port: url.port || (isHttps ? 443 : 80),
|
||||
path: url.pathname + url.search,
|
||||
method: options.method || 'GET',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
...options.headers,
|
||||
},
|
||||
timeout: 30000,
|
||||
};
|
||||
|
||||
// Disable certificate verification for problematic sites
|
||||
if (isHttps) {
|
||||
requestOptions.rejectUnauthorized = false;
|
||||
}
|
||||
|
||||
const req = client.request(requestOptions, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
resolve({
|
||||
statusCode: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: data,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', reject);
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timeout'));
|
||||
});
|
||||
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* EZTV API Proxy
|
||||
* Proxies requests to https://eztvx.to/api
|
||||
*/
|
||||
router.get('/eztv/*', async (req, res) => {
|
||||
try {
|
||||
// Build the target URL
|
||||
const path = req.params[0] || '';
|
||||
const queryString = new URLSearchParams(req.query).toString();
|
||||
const targetUrl = `https://eztvx.to/api/${path}${queryString ? '?' + queryString : ''}`;
|
||||
|
||||
console.log(`[Proxy] EZTV request: ${targetUrl}`);
|
||||
|
||||
const response = await makeRequest(targetUrl);
|
||||
|
||||
// Set CORS headers
|
||||
res.set('Access-Control-Allow-Origin', '*');
|
||||
res.set('Content-Type', 'application/json');
|
||||
|
||||
res.status(response.statusCode).send(response.body);
|
||||
} catch (error) {
|
||||
console.error('[Proxy] EZTV error:', error.message);
|
||||
res.status(500).json({
|
||||
error: 'Proxy error',
|
||||
message: error.message,
|
||||
torrents: [],
|
||||
torrents_count: 0
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* YTS API Proxy
|
||||
* Proxies requests to https://yts.mx/api/v2
|
||||
*/
|
||||
router.get('/yts/*', async (req, res) => {
|
||||
try {
|
||||
const path = req.params[0] || '';
|
||||
const queryString = new URLSearchParams(req.query).toString();
|
||||
const targetUrl = `https://yts.mx/api/v2/${path}${queryString ? '?' + queryString : ''}`;
|
||||
|
||||
console.log(`[Proxy] YTS request: ${targetUrl}`);
|
||||
|
||||
const response = await makeRequest(targetUrl);
|
||||
|
||||
res.set('Access-Control-Allow-Origin', '*');
|
||||
res.set('Content-Type', 'application/json');
|
||||
|
||||
res.status(response.statusCode).send(response.body);
|
||||
} catch (error) {
|
||||
console.error('[Proxy] YTS error:', error.message);
|
||||
res.status(500).json({
|
||||
error: 'Proxy error',
|
||||
message: error.message,
|
||||
data: { movies: [] }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* TMDB API Proxy
|
||||
* Proxies requests to https://api.themoviedb.org/3
|
||||
*/
|
||||
router.get('/tmdb/*', async (req, res) => {
|
||||
try {
|
||||
const path = req.params[0] || '';
|
||||
const queryString = new URLSearchParams(req.query).toString();
|
||||
const targetUrl = `https://api.themoviedb.org/3/${path}${queryString ? '?' + queryString : ''}`;
|
||||
|
||||
console.log(`[Proxy] TMDB request: ${targetUrl}`);
|
||||
|
||||
const response = await makeRequest(targetUrl);
|
||||
|
||||
res.set('Access-Control-Allow-Origin', '*');
|
||||
res.set('Content-Type', 'application/json');
|
||||
|
||||
res.status(response.statusCode).send(response.body);
|
||||
} catch (error) {
|
||||
console.error('[Proxy] TMDB error:', error.message);
|
||||
res.status(500).json({
|
||||
error: 'Proxy error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { router as proxyRouter };
|
||||
@@ -132,9 +132,12 @@ streamRouter.get('/:sessionId/video', async (req, res) => {
|
||||
|
||||
const stream = torrentManager.createReadStream(sessionId, { start, end });
|
||||
|
||||
// Handle stream errors gracefully
|
||||
// Handle stream errors gracefully - suppress common client disconnect errors
|
||||
stream.on('error', (err) => {
|
||||
console.error('Stream error:', err.message);
|
||||
// Only log unexpected errors, not normal client disconnects
|
||||
if (!err.message?.includes('closed prematurely') && !err.message?.includes('ECONNRESET')) {
|
||||
console.error('Stream error:', err.message);
|
||||
}
|
||||
if (!res.headersSent) {
|
||||
res.status(500).end();
|
||||
}
|
||||
@@ -156,9 +159,12 @@ streamRouter.get('/:sessionId/video', async (req, res) => {
|
||||
|
||||
const stream = torrentManager.createReadStream(sessionId);
|
||||
|
||||
// Handle stream errors gracefully
|
||||
// Handle stream errors gracefully - suppress common client disconnect errors
|
||||
stream.on('error', (err) => {
|
||||
console.error('Stream error:', err.message);
|
||||
// Only log unexpected errors, not normal client disconnects
|
||||
if (!err.message?.includes('closed prematurely') && !err.message?.includes('ECONNRESET')) {
|
||||
console.error('Stream error:', err.message);
|
||||
}
|
||||
if (!res.headersSent) {
|
||||
res.status(500).end();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import WebTorrent from 'webtorrent';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { getCacheDir, isAndroid } from '../utils/platformPaths.js';
|
||||
|
||||
// Get cache directory using platform-aware utility
|
||||
function getCacheDirectory() {
|
||||
return getCacheDir('bestream-cache');
|
||||
}
|
||||
|
||||
// Trackers for magnet links
|
||||
const TRACKERS = [
|
||||
@@ -17,11 +22,20 @@ const TRACKERS = [
|
||||
|
||||
class TorrentManager {
|
||||
constructor() {
|
||||
this.client = new WebTorrent({
|
||||
// Configure WebTorrent options
|
||||
const webTorrentOptions = {
|
||||
maxConns: 100,
|
||||
});
|
||||
};
|
||||
|
||||
// Disable uTP on Android (native module not supported)
|
||||
if (isAndroid()) {
|
||||
webTorrentOptions.utp = false;
|
||||
console.log('ℹ️ Running on Android - uTP disabled');
|
||||
}
|
||||
|
||||
this.client = new WebTorrent(webTorrentOptions);
|
||||
this.sessions = new Map();
|
||||
this.downloadPath = path.join(os.tmpdir(), 'bestream-cache');
|
||||
this.downloadPath = getCacheDirectory();
|
||||
|
||||
// Ensure download directory exists
|
||||
if (!fs.existsSync(this.downloadPath)) {
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { getCacheDir, isAndroid } from '../utils/platformPaths.js';
|
||||
|
||||
class Transcoder {
|
||||
constructor() {
|
||||
this.hlsPath = path.join(os.tmpdir(), 'bestream-hls');
|
||||
this.hlsPath = getCacheDir('bestream-hls');
|
||||
this.activeTranscodes = new Map();
|
||||
this.ffmpegAvailable = false;
|
||||
|
||||
// Skip FFmpeg setup on Android - it's not available
|
||||
if (isAndroid()) {
|
||||
// FFmpeg is not bundled with the Android app
|
||||
// Direct video streaming will be used instead
|
||||
console.log(`📺 HLS output directory: ${this.hlsPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure HLS directory exists
|
||||
if (!fs.existsSync(this.hlsPath)) {
|
||||
@@ -38,16 +47,18 @@ class Transcoder {
|
||||
}
|
||||
console.log(`📺 FFmpeg configured at: ${ffmpegPath}`);
|
||||
ffmpegFound = true;
|
||||
this.ffmpegAvailable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no FFmpeg found in common locations, warn user
|
||||
// If no FFmpeg found in common locations, it's optional
|
||||
if (!ffmpegFound) {
|
||||
console.warn('⚠️ FFmpeg not found in common locations. Make sure FFmpeg is installed and in your PATH.');
|
||||
console.warn(' Install FFmpeg: winget install Gyan.FFmpeg');
|
||||
console.warn(' Or download from: https://ffmpeg.org/download.html');
|
||||
console.log('📺 FFmpeg not found - HLS transcoding disabled (direct streaming will be used)');
|
||||
}
|
||||
} else if (process.platform !== 'android') {
|
||||
// On Linux/Mac, assume FFmpeg might be in PATH
|
||||
this.ffmpegAvailable = true;
|
||||
}
|
||||
|
||||
console.log(`📺 HLS output directory: ${this.hlsPath}`);
|
||||
@@ -58,6 +69,10 @@ class Transcoder {
|
||||
*/
|
||||
getVideoInfo(inputPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.ffmpegAvailable) {
|
||||
reject(new Error('FFmpeg not available'));
|
||||
return;
|
||||
}
|
||||
ffmpeg.ffprobe(inputPath, (err, metadata) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
@@ -91,6 +106,11 @@ class Transcoder {
|
||||
* Start HLS transcoding
|
||||
*/
|
||||
async startHlsTranscode(sessionId, inputPath, onProgress, quality = null) {
|
||||
// Check if FFmpeg is available
|
||||
if (!this.ffmpegAvailable) {
|
||||
throw new Error('FFmpeg not available - use direct streaming instead');
|
||||
}
|
||||
|
||||
const outputDir = path.join(this.hlsPath, sessionId);
|
||||
const playlistPath = path.join(outputDir, 'playlist.m3u8');
|
||||
|
||||
@@ -190,6 +210,11 @@ class Transcoder {
|
||||
* This allows transcoding to start even when the file isn't fully on disk
|
||||
*/
|
||||
async startHlsStreamTranscode(sessionId, inputStream, onProgress, quality = null) {
|
||||
// Check if FFmpeg is available
|
||||
if (!this.ffmpegAvailable) {
|
||||
throw new Error('FFmpeg not available - use direct streaming instead');
|
||||
}
|
||||
|
||||
const outputDir = path.join(this.hlsPath, sessionId);
|
||||
const playlistPath = path.join(outputDir, 'playlist.m3u8');
|
||||
|
||||
|
||||
99
server/src/utils/platformPaths.js
Normal file
99
server/src/utils/platformPaths.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Platform-aware path utilities for Android compatibility
|
||||
* On Android (capacitor-nodejs), os.tmpdir() returns '/tmp' which is not writable.
|
||||
* This module provides utilities to get writable directories on all platforms.
|
||||
*/
|
||||
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Cache the detected values
|
||||
let _isAndroid = null;
|
||||
let _dataDir = null;
|
||||
|
||||
/**
|
||||
* Detect if running on Android (capacitor-nodejs environment)
|
||||
*/
|
||||
export function isAndroid() {
|
||||
if (_isAndroid !== null) return _isAndroid;
|
||||
|
||||
const tmpDir = os.tmpdir();
|
||||
const cwd = process.cwd();
|
||||
|
||||
// Multiple detection methods for Android:
|
||||
// 1. process.platform is 'android' on capacitor-nodejs
|
||||
// 2. tmpdir is '/tmp' (not writable on Android)
|
||||
// 3. cwd contains Android app paths
|
||||
_isAndroid =
|
||||
process.platform === 'android' ||
|
||||
tmpDir === '/tmp' ||
|
||||
cwd.includes('/data/data/') ||
|
||||
cwd.includes('/data/user/');
|
||||
|
||||
return _isAndroid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base data directory for the app.
|
||||
* On Android: /data/data/com.bestream.app/files/
|
||||
* On Desktop: System temp directory
|
||||
*/
|
||||
export function getDataDirectory() {
|
||||
if (_dataDir !== null) return _dataDir;
|
||||
|
||||
if (isAndroid()) {
|
||||
try {
|
||||
// Derive from import.meta.url which gives us the bundle location
|
||||
// Bundle runs from: file:///data/data/com.bestream.app/files/nodejs/public/index.js
|
||||
// We want: /data/data/com.bestream.app/files/
|
||||
const currentFileUrl = import.meta.url;
|
||||
const currentFilePath = fileURLToPath(currentFileUrl);
|
||||
|
||||
// Go up to find the 'files' directory
|
||||
let dir = path.dirname(currentFilePath);
|
||||
while (dir && dir !== '/' && !dir.endsWith('/files')) {
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir) break; // Reached root
|
||||
dir = parent;
|
||||
}
|
||||
|
||||
_dataDir = dir;
|
||||
console.log(`[platformPaths] Android data directory: ${_dataDir}`);
|
||||
} catch (e) {
|
||||
// Fallback to hardcoded path
|
||||
_dataDir = '/data/data/com.bestream.app/files';
|
||||
console.log(`[platformPaths] Using fallback Android path: ${_dataDir}`);
|
||||
}
|
||||
} else {
|
||||
_dataDir = os.tmpdir();
|
||||
console.log(`[platformPaths] Desktop temp directory: ${_dataDir}`);
|
||||
}
|
||||
|
||||
return _dataDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a writable cache directory for a specific purpose.
|
||||
* Creates the directory if it doesn't exist.
|
||||
* @param {string} name - The subdirectory name (e.g., 'bestream-cache', 'bestream-hls')
|
||||
* @returns {string} The full path to the cache directory
|
||||
*/
|
||||
export function getCacheDir(name) {
|
||||
const baseDir = getDataDirectory();
|
||||
return path.join(baseDir, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a temporary file path that works on all platforms
|
||||
* @param {string} filename - The filename
|
||||
* @returns {string} Full path to the temp file
|
||||
*/
|
||||
export function getTempFilePath(filename) {
|
||||
const baseDir = getDataDirectory();
|
||||
return path.join(baseDir, 'temp', filename);
|
||||
}
|
||||
|
||||
// Log platform info on first import
|
||||
const platform = isAndroid() ? 'Android' : process.platform;
|
||||
console.log(`[platformPaths] Detected platform: ${platform}`);
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import { isCapacitor, getApiUrl } from '../../utils/platform';
|
||||
|
||||
// TMDB API for TV show metadata and discovery
|
||||
// Using a proxy to avoid CORS issues
|
||||
@@ -7,7 +8,14 @@ const TMDB_BASE_URL = 'https://api.themoviedb.org/3'; // Temporarily bypass prox
|
||||
const TMDB_IMAGE_BASE = 'https://image.tmdb.org/t/p/';
|
||||
|
||||
// EZTV API for torrents
|
||||
const EZTV_BASE_URL = '/api/eztv'; // Proxied through Vite
|
||||
// On Capacitor (Android), use the Node.js server proxy
|
||||
// On web/dev, use Vite's proxy
|
||||
const getEztvBaseUrl = () => {
|
||||
if (isCapacitor()) {
|
||||
return `${getApiUrl()}/api/eztv`;
|
||||
}
|
||||
return '/api/eztv'; // Proxied through Vite in dev
|
||||
};
|
||||
|
||||
// EZTV authentication (optional - for accessing more content)
|
||||
// Set these in your environment variables
|
||||
@@ -331,7 +339,7 @@ export const tvDiscoveryApi = {
|
||||
console.log(`[EZTV] Fetching torrents for IMDB ID: ${numericId} (original: ${imdbId}) ${isAuthenticated ? '(authenticated)' : '(anonymous)'}`);
|
||||
|
||||
// Use the working eztvx.to API
|
||||
const response = await axios.get(`${EZTV_BASE_URL}/get-torrents`, {
|
||||
const response = await axios.get(`${getEztvBaseUrl()}/get-torrents`, {
|
||||
params: {
|
||||
imdb_id: numericId,
|
||||
limit: 100,
|
||||
@@ -408,7 +416,7 @@ export const tvDiscoveryApi = {
|
||||
console.log(`[Episode Search] Searching for S${seasonStr}E${episodeStr} with IMDB: ${numericId}`);
|
||||
|
||||
try {
|
||||
const response = await axios.get(`${EZTV_BASE_URL}/get-torrents`, {
|
||||
const response = await axios.get(`${getEztvBaseUrl()}/get-torrents`, {
|
||||
params: {
|
||||
imdb_id: numericId,
|
||||
limit: 100, // Get plenty of results
|
||||
|
||||
Reference in New Issue
Block a user