public void play(final Uri uri) {
        execute(() -> {
            releaseInternal();
            this.player = new MediaPlayer();
            this.uri = uri;
            final int thisSongId = songId.addAndGet(1);
            this.looperThreadDisposable = AndroidRxUtils.runPeriodically(AndroidRxUtils.computationScheduler, () -> {
                synchronized (mutex) {
                    if (state == State.PLAYING) {
                        //in case we reached duration already, we set "COMPLETED" here ourselfes
                        setState(duration == getCurrentPosition() ? State.COMPLETED : State.PLAYING, true);
                    }
                }
            }, 0, 500);
            startPlayer(this.player, this.uri, this.mute, success -> {
                synchronized (mutex) {
                    if (songId.get() != thisSongId) {
                        //this is from another song and can be ignored
                        return;
                    }
                    this.timeLastStartTimestamp = -1;
                    if (!success) {
                        safeReleasePlayer(this.player);
                        setState(State.ERROR, false);
                        this.timePassedBeforeLastStart = 0;
                    } else {
                        setState(State.COMPLETED, false);
                        this.timePassedBeforeLastStart = this.duration;
                    }
                }
            });
            this.duration = player.getDuration();
            this.timePassedBeforeLastStart = 0;
            this.timeLastStartTimestamp = System.currentTimeMillis();
            setState(State.PLAYING, false);
        });
    }
Represents and manages one audio file or tone
main/src/main/java/cgeo/geocaching/utils/AudioManager.java