net.sf.asap.Player.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.asap.Player.java

Source

/*
 * Player.java - ASAP for Android
 *
 * Copyright (C) 2010-2011  Piotr Fusik
 *
 * This file is part of ASAP (Another Slight Atari Player),
 * see http://asap.sourceforge.net
 *
 * ASAP is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * ASAP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ASAP; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package net.sf.asap;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.MediaController;
import android.widget.TextView;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
import java.util.zip.ZipFile;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

public class Player extends Activity implements Runnable {
    private final ASAP asap = new ASAP();
    private ASAPInfo info;
    private String filename;
    private int song;
    private MediaController mediaController;
    private boolean stop;
    private AudioTrack audioTrack;

    private View getContentView() {
        ViewGroup vg = (ViewGroup) getWindow().getDecorView();
        return vg.getChildAt(0);
    }

    private void showError(int messageId) {
        setTitle(R.string.error_title);
        setContentView(R.layout.error);
        ((TextView) findViewById(R.id.filename)).setText(filename);
        ((TextView) findViewById(R.id.message)).setText(messageId);
    }

    private void setTag(int controlId, String value) {
        TextView control = (TextView) findViewById(controlId);
        if (value.length() == 0)
            control.setVisibility(View.GONE);
        else {
            control.setText(value);
            control.setVisibility(View.VISIBLE);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mediaController == null)
            return false;
        mediaController.show();
        return true;
    }

    private boolean isPaused() {
        return audioTrack == null || audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED;
    }

    private void resume() {
        if (audioTrack != null) {
            audioTrack.play();
            synchronized (this) {
                notify();
            }
        }
    }

    private void playSong(int song) {
        this.song = song;
        try {
            synchronized (asap) {
                asap.playSong(song, info.getLoop(song) ? -1 : info.getDuration(song));
            }
        } catch (Exception ex) {
            showError(R.string.invalid_file);
            return;
        }
        resume();
        int songs = info.getSongs();
        if (songs > 1)
            setTag(R.id.song, getString(R.string.song_format, song + 1, songs));
        else
            setTag(R.id.song, "");
    }

    private void playNextSong() {
        if (song + 1 < info.getSongs())
            playSong(song + 1);
    }

    private void playPreviousSong() {
        if (song > 0)
            playSong(song - 1);
    }

    private void seek(int pos) {
        try {
            synchronized (asap) {
                asap.seek(pos);
            }
        } catch (Exception ex) {
            showError(R.string.invalid_file);
        }
    }

    public void run() {
        int config = info.getChannels() == 1 ? AudioFormat.CHANNEL_CONFIGURATION_MONO
                : AudioFormat.CHANNEL_CONFIGURATION_STEREO;
        int len = AudioTrack.getMinBufferSize(ASAP.SAMPLE_RATE, config, AudioFormat.ENCODING_PCM_8BIT);
        if (len < 16384)
            len = 16384;
        final byte[] buffer = new byte[len];
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, ASAP.SAMPLE_RATE, config,
                AudioFormat.ENCODING_PCM_8BIT, len, AudioTrack.MODE_STREAM);
        audioTrack.play();

        for (;;) {
            synchronized (this) {
                if (len < buffer.length || isPaused()) {
                    try {
                        wait();
                    } catch (InterruptedException ex) {
                    }
                }
                if (stop) {
                    audioTrack.stop();
                    return;
                }
            }
            synchronized (asap) {
                len = asap.generate(buffer, buffer.length, ASAPSampleFormat.U8);
            }
            audioTrack.write(buffer, 0, len);
        }
    }

    private static InputStream httpGet(Uri uri) throws IOException {
        DefaultHttpClient client = new DefaultHttpClient();
        HttpGet request = new HttpGet(uri.toString());
        HttpResponse response = client.execute(request);
        StatusLine status = response.getStatusLine();
        if (status.getStatusCode() != 200)
            throw new IOException("HTTP error " + status);
        return response.getEntity().getContent();
    }

    /**
     * Reads bytes from the stream into the byte array
     * until end of stream or array is full.
     * @param is source stream
     * @param b output array
     * @return number of bytes read
     */
    private static int readAndClose(InputStream is, byte[] b) throws IOException {
        int got = 0;
        int len = b.length;
        try {
            while (got < len) {
                int i = is.read(b, got, len - got);
                if (i <= 0)
                    break;
                got += i;
            }
        } finally {
            is.close();
        }
        return got;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Uri uri = getIntent().getData();
        filename = uri.getLastPathSegment();
        ZipFile zip = null;
        final byte[] module = new byte[ASAPInfo.MAX_MODULE_LENGTH];
        int moduleLen;
        try {
            InputStream is;
            if (Util.isZip(filename)) {
                zip = new ZipFile(uri.getPath());
                filename = uri.getFragment();
                is = zip.getInputStream(zip.getEntry(filename));
            } else {
                try {
                    is = getContentResolver().openInputStream(uri);
                } catch (FileNotFoundException ex) {
                    if (uri.getScheme().equals("http"))
                        is = httpGet(uri);
                    else
                        throw ex;
                }
            }
            moduleLen = readAndClose(is, module);
        } catch (IOException ex) {
            showError(R.string.error_reading_file);
            return;
        } finally {
            Util.close(zip);
        }
        try {
            asap.load(filename, module, moduleLen);
        } catch (Exception ex) {
            showError(R.string.invalid_file);
            return;
        }
        info = asap.getInfo();

        setTitle(R.string.playing_title);
        setContentView(R.layout.playing);
        setTag(R.id.name, info.getTitleOrFilename());
        setTag(R.id.author, info.getAuthor());
        setTag(R.id.date, info.getDate());
        findViewById(R.id.stop_button).setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                finish();
            }
        });
        mediaController = new MediaController(this, false);
        mediaController.setAnchorView(getContentView());
        mediaController.setMediaPlayer(new MediaController.MediaPlayerControl() {
            public boolean canPause() {
                return !isPaused();
            }

            public boolean canSeekBackward() {
                return false;
            }

            public boolean canSeekForward() {
                return false;
            }

            public int getBufferPercentage() {
                return 100;
            }

            public int getCurrentPosition() {
                return asap.getPosition();
            }

            public int getDuration() {
                return info.getDuration(song);
            }

            public boolean isPlaying() {
                return !isPaused();
            }

            public void pause() {
                audioTrack.pause();
            }

            public void seekTo(int pos) {
                seek(pos);
            }

            public void start() {
                resume();
            }
        });
        if (info.getSongs() > 1) {
            mediaController.setPrevNextListeners(new OnClickListener() {
                public void onClick(View v) {
                    playNextSong();
                }
            }, new OnClickListener() {
                public void onClick(View v) {
                    playPreviousSong();
                }
            });
        }
        new Handler().postDelayed(new Runnable() {
            public void run() {
                mediaController.show();
            }
        }, 500);

        stop = false;
        playSong(info.getDefaultSong());
        new Thread(this).start();
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (mediaController == null) {
            // error shown
            return super.onKeyDown(keyCode, event);
        }
        switch (keyCode) {
        case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
            if (isPaused())
                resume();
            else
                audioTrack.pause();
            return true;
        case KeyEvent.KEYCODE_MEDIA_NEXT:
            playNextSong();
            return true;
        case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
            playPreviousSong();
            return true;
        default:
            return super.onKeyDown(keyCode, event);
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        synchronized (this) {
            stop = true;
            notify();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return Util.onCreateOptionsMenu(this, menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return Util.onOptionsItemSelected(this, item);
    }
}