Looking to report an issue/bug or make a feature request? Please refer to the README file.
Thanks for your interest in contributing to REX Player!
This guide outlines our development practices, code guidelines, git workflow, and translation contribution process.
We welcome bug fixes, performance optimizations, and feature enhancements.
- SDK/JDK Requirements: Java 17
- Build Tool: Gradle
- Build Debug APK:
./gradlew assembleDebug
- Build Release APK:
./gradlew assembleRelease
There is no automated test suite. Contributors must perform manual verification on a physical Android device or emulator before submitting a Pull Request. Check Android Logcat during playback to ensure no unexpected exceptions or errors are thrown.
We follow a modular, Ops/Manager-driven architecture to keep our UI controllers thin and testing simple.
- ViewModels as Coordinators: ViewModels (like
PlayerViewModel) should only manage UI state representation and delegate all business logic to dedicated Manager classes. - Managers for Domain Logic: Specialized operations belong in manager classes (e.g.,
PlaybackManagerfor seeking,PlaylistManagerfor shuffling,SubtitleManagerfor downloads). - Unified Media Scanning: Never scan the filesystem directly. Use
CoreMediaScanner(viaFileSystemOpsandMediaMetadataOps) for all media file discovery to ensure folder counts and badge states are handled correctly. - UI Consistency: Use
BaseMediaCardfor any list or grid media representations to maintain aspect ratio and badge styling consistency.
// 1. Player View (Jetpack Compose) triggers an action on the coordinator
PlayPauseButton(onClick = { playerViewModel.togglePlayPause() })
// 2. Coordinator (ViewModel) delegates straight to the PlaybackManager
class PlayerViewModel(
private val playbackManager: PlaybackManager
) : ViewModel() {
fun togglePlayPause() {
playbackManager.togglePlay()
}
}
// 3. Manager (PlaybackManager) handles the direct domain/video engine logic
class PlaybackManager(private val mpvLib: MPVLib) {
private val _isPlaying = MutableStateFlow(false)
val isPlaying = _isPlaying.asStateFlow()
fun togglePlay() {
val newState = !_isPlaying.value
mpvLib.setPropertyBoolean("pause", !newState)
_isPlaying.value = newState
}
}If you are porting code, patterns, or assets from other media players (e.g., mpv-android, mpvEx, mpvKt, Next Player, Gramophone), you must disclose this in your Pull Request description. Include links to the source repository and ensure the license is compatible with our Apache License 2.0.
To keep the repository history clean and manageable, we adhere to the following Git rules:
- Feature Branches: Create a new branch for every feature or fix, branched off
master. - Atomic Commits: Separate structural refactoring from visual/functional fixes into distinct commits.
- Keep Branches Updated: Use
rebaseinstead of merge to bring your branch up to date withmaster. - Commit Messages: We follow Conventional Commits (e.g.
fix:,feat:,refactor:,chore:) to keep our commit history clear and organized. You are responsible for keeping your commits clean and conforming; non-conforming commits will need to be reworded or squashed by you before merge.
- Target the
masterbranch. - Keep PRs scoped to a single fix or feature — split unrelated changes into separate PRs.
- Reference the related issue number if one exists.
- Describe what changed and why; the diff already shows how.
GitHub Issues and Pull Request discussions are our primary channels for communication. Feel free to open a draft issue to discuss design proposals before writing significant code.
For translations, we use Droidlate (PyPI) — a lightweight, local, web-based translation workspace designed specifically for Android strings.xml resource files.
- Local Offline Workspace: Translate and edit local files on your own machine without uploading resources to third-party servers.
- Preserving XML Formatting & Comments: Maintain exact XML styling, comments, structure, and formatting.
- Tracking Outdated Translations: Highlight which target translations need updates when base strings change.
- Placeholder Verification & QA: Validate Java-style placeholders to prevent runtime crashes.
- Orphaned String Pruning: Easily prune strings deleted from the main codebase.
You can install Droidlate globally via pipx (recommended) or standard pip:
# Using pipx (recommended)
pipx install droidlate
# Or using normal pip
pip install droidlateEnsure you have cloned the latest code for REX Player:
git clone https://github.com/mpvRex/REX-Player.git
cd REX-PlayerOpen your terminal in the root of the REX-Player directory and run:
droidlateDroidlate will start a local web server and open the interface in your default web browser (usually at http://127.0.0.1:5000).
If your locale isn't listed on the dashboard:
- Create a new directory named
values-<locale>inside your resource directory (e.g.,app/src/main/res/values-<locale>) following Android's locale qualifier format. - Place an empty
strings.xmlfile inside that directory. - Restart Droidlate — it will detect the new locale, populate it with the base English strings, and allow you to begin translating.
On the dashboard, select your language locale to open the editor.
- Translate strings, view developer comments, and check for format placeholder warnings.
- Use keyboard shortcuts for productivity:
Ctrl + S: Instantly Save & Next.Alt + 1/Alt + 2: Paste auto-translation suggestions (Google Translate or MyMemory).
If any strings have been deleted from the English file, check the "Orphaned" tab in the editor to safely prune them and keep the files clean.
Saving changes updates:
- The target XML file (e.g.,
app/src/main/res/values-<locale>/strings.xml) - A tracking file inside the
.translation_metadata/directory (e.g.,.translation_metadata/values-<locale>.json)
Important: You must commit both the updated strings.xml and its corresponding .json ledger file to Git.