com.piusvelte.webcaster.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.piusvelte.webcaster.MainActivity.java

Source

/*
 * WebCaster - Chromecast Web Media Library
 * Copyright (C) 2013 Bryan Emmanuel
 *
 * This program 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 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  Bryan Emmanuel piusvelte@gmail.com
 */
package com.piusvelte.webcaster;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import com.google.cast.ApplicationChannel;
import com.google.cast.ApplicationMetadata;
import com.google.cast.ApplicationSession;
import com.google.cast.CastContext;
import com.google.cast.CastDevice;
import com.google.cast.ContentMetadata;
import com.google.cast.MediaProtocolCommand;
import com.google.cast.MediaProtocolMessageStream;
import com.google.cast.MediaProtocolMessageStream.PlayerState;
import com.google.cast.MediaRouteAdapter;
import com.google.cast.MediaRouteHelper;
import com.google.cast.MediaRouteStateChangeListener;
import com.google.cast.SessionError;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.support.v7.app.MediaRouteButton;
import android.support.v7.app.MediaRouteDialogFactory;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;

public class MainActivity extends FragmentActivity implements LoaderCallbacks<List<Medium>>,
        MediaListFragment.Listener, MediaRouteAdapter, OnClickListener, OnSeekBarChangeListener {

    private static final String TAG = "MainActivity";
    protected static final double MAX_VOLUME_LEVEL = 20;
    private static final double VOLUME_INCREMENT = 0.05;
    private static final int FRAGMENT_MEDIA_ROOT = 0;

    private String mediaHost = null;
    private String appId = null;
    private List<Medium> media = new ArrayList<Medium>();
    private List<Integer> dirIdx = new ArrayList<Integer>();
    private DirPagerAdapter dirPagerAdapter;
    private ViewPager viewPager;
    private CastContext castContext;
    private MediaRouter mediaRouter;
    private MediaRouteSelector mediaRouteSelector;
    private MediaRouteButton mediaRouteButton;
    private MediaRouter.Callback mediaRouterCallback;
    private CastDevice castDevice;
    private ApplicationSession applicationSession;
    private MediaProtocolMessageStream mediaProtocolMessageStream;
    private Button btnPlay;
    private MediaRouteStateChangeListener mediaRouteStateChangeListener;
    private SeekBar seekBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create the adapter that will return a fragment for each of the three
        // primary sections of the app.
        dirPagerAdapter = new DirPagerAdapter(getSupportFragmentManager());

        // Set up the ViewPager with the sections adapter.
        viewPager = (ViewPager) findViewById(R.id.pager);
        viewPager.setAdapter(dirPagerAdapter);

        castContext = new CastContext(getApplicationContext());

        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
        mediaHost = sp.getString(getString(R.string.preference_host), null);
        appId = sp.getString(getString(R.string.preference_app_id), null);

        if (appId != null) {
            setupCasting();
        }

        seekBar = (SeekBar) findViewById(R.id.seek);
        seekBar.setOnSeekBarChangeListener(this);

        btnPlay = (Button) findViewById(R.id.btn_play);
        btnPlay.setOnClickListener(this);

        getActionBar().setDisplayHomeAsUpEnabled(true);

        if (mediaHost != null) {
            getSupportLoaderManager().initLoader(0, null, this);
        }
    }

    private void setupCasting() {
        MediaRouteHelper.registerMinimalMediaRouteProvider(castContext, this);
        mediaRouter = MediaRouter.getInstance(getApplicationContext());
        mediaRouteSelector = MediaRouteHelper.buildMediaRouteSelector(MediaRouteHelper.CATEGORY_CAST, appId, null);

        mediaRouterCallback = new MediaRouter.Callback() {
            @Override
            public void onRouteSelected(MediaRouter router, RouteInfo route) {
                MediaRouteHelper.requestCastDeviceForRoute(route);
            }

            @Override
            public void onRouteUnselected(MediaRouter router, RouteInfo route) {
                try {
                    if (applicationSession != null) {
                        applicationSession.setStopApplicationWhenEnding(true);
                        applicationSession.endSession();
                    } else {
                        Log.e(TAG, "onRouteUnselected: mSession is null");
                    }
                } catch (IllegalStateException e) {
                    Log.e(TAG, "onRouteUnselected:");
                    e.printStackTrace();
                } catch (IOException e) {
                    Log.e(TAG, "onRouteUnselected:");
                    e.printStackTrace();
                }
                mediaProtocolMessageStream = null;
                castDevice = null;
                mediaRouteStateChangeListener = null;
            }
        };
    }

    private void clearCasting() {
        try {
            if (applicationSession != null) {
                applicationSession.setStopApplicationWhenEnding(true);
                applicationSession.endSession();
            } else {
                Log.e(TAG, "onRouteUnselected: mSession is null");
            }
        } catch (IllegalStateException e) {
            Log.e(TAG, "onRouteUnselected:");
            e.printStackTrace();
        } catch (IOException e) {
            Log.e(TAG, "onRouteUnselected:");
            e.printStackTrace();
        }
        mediaProtocolMessageStream = null;
        castDevice = null;
        mediaRouteStateChangeListener = null;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);

        MenuItem mediaRouteItem = menu.findItem(R.id.action_mediaroute);

        if (mediaRouteSelector != null) {
            mediaRouteButton = (MediaRouteButton) mediaRouteItem.getActionView();
            mediaRouteButton.setRouteSelector(mediaRouteSelector);
            mediaRouteButton.setDialogFactory(new MediaRouteDialogFactory());
            mediaRouteItem.setVisible(true);
            mediaRouteItem.setEnabled(true);
        } else {
            mediaRouteItem.setVisible(false);
            mediaRouteItem.setEnabled(false);
        }

        return true;
    }

    @Override
    protected void onStart() {
        super.onStart();

        if (mediaRouter != null) {
            mediaRouter.addCallback(mediaRouteSelector, mediaRouterCallback,
                    MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        if ((appId == null) || (mediaHost == null)) {
            startActivityForResult(
                    new Intent(this, SettingsActivity.class).putExtra(SettingsActivity.EXTRA_APP_ID, appId)
                            .putExtra(SettingsActivity.EXTRA_HOST, mediaHost),
                    0);
        }

        if (applicationSession != null) {
            try {
                applicationSession.resumeSession();
            } catch (IOException e) {
                Log.e(TAG, "No session to resume", e);
            } catch (IllegalStateException e) {
                Log.e(TAG, "No session to resume", e);
            } catch (RuntimeException e) {
                Log.e(TAG, "No session to resume", e);
            }
        }
    }

    @Override
    protected void onActivityResult(int resultCode, int resultType, Intent data) {

        resultCode = resultType >> 16;

        if ((resultCode == RESULT_OK) && (data != null)) {
            String resultAppId = data.getStringExtra(SettingsActivity.EXTRA_APP_ID);

            if (((resultAppId != null) && (!resultAppId.equals(appId)) || (appId != null))) {
                clearCasting();
                appId = resultAppId;

                if (appId != null) {
                    setupCasting();
                }

                invalidateOptionsMenu();

            }

            String resultHost = data.getStringExtra(SettingsActivity.EXTRA_HOST);

            if (((resultHost != null) && (!resultHost.equals(mediaHost)) || (mediaHost != null))) {
                mediaHost = resultHost;
                media = new ArrayList<Medium>();
                dirIdx.clear();
                dirIdx.add(0);
                dirPagerAdapter.notifyDataSetChanged();
                MediaLoader loader = (MediaLoader) getSupportLoaderManager().initLoader(0, null, this);

                if (loader != null) {
                    loader.loadHost(mediaHost);
                }
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        PreferenceManager.getDefaultSharedPreferences(this).edit()
                .putString(getString(R.string.preference_app_id), appId)
                .putString(getString(R.string.preference_host), mediaHost).commit();
    }

    /**
     * Closes a running session upon destruction of this Activity.
     */
    @Override
    protected void onStop() {
        if (mediaRouter != null) {
            mediaRouter.removeCallback(mediaRouterCallback);
        }

        super.onStop();
    }

    @Override
    protected void onDestroy() {
        if (applicationSession != null) {
            try {
                if (!applicationSession.hasStopped()) {
                    applicationSession.endSession();
                }
            } catch (IOException e) {
                Log.e(TAG, "Failed to end session.");
            }
        }

        applicationSession = null;
        super.onDestroy();
    }

    public class DirPagerAdapter extends FragmentPagerAdapter {

        public DirPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            Fragment fragment = new MediaListFragment();
            Bundle args = new Bundle();
            args.putInt(MediaListFragment.EXTRA_DIR_POSITION, position);
            fragment.setArguments(args);
            return fragment;
        }

        @Override
        public int getCount() {
            return dirIdx.size();
        }

        @Override
        public CharSequence getPageTitle(int position) {
            Locale l = Locale.getDefault();

            if (position == FRAGMENT_MEDIA_ROOT) {
                return mediaHost.toUpperCase(l);
            } else {
                Medium m = getMediumAt(position);
                if (m != null) {
                    return m.getFile().substring(m.getFile().lastIndexOf(File.separator) + 1).toUpperCase(l);
                } else {
                    return "";
                }
            }
        }
    }

    private void openSession() {
        applicationSession = new ApplicationSession(castContext, castDevice);

        // TODO: The below lines allow you to specify either that your
        // application uses the default
        // implementations of the Notification and Lock Screens, or that you
        // will be using your own.
        int flags = 0;

        // Comment out the below line if you are not writing your own
        // Notification Screen.
        // flags |= ApplicationSession.FLAG_DISABLE_NOTIFICATION;

        // Comment out the below line if you are not writing your own Lock
        // Screen.
        // flags |= ApplicationSession.FLAG_DISABLE_LOCK_SCREEN_REMOTE_CONTROL;
        applicationSession.setApplicationOptions(flags);

        applicationSession.setListener(new ApplicationSession.Listener() {

            @Override
            public void onSessionStarted(ApplicationMetadata appMetadata) {
                ApplicationChannel channel = applicationSession.getChannel();

                if (channel == null) {
                    Log.e(TAG, "channel = null");
                    return;
                }

                if (mediaProtocolMessageStream != null) {
                    mediaProtocolMessageStream = null;
                }

                mediaProtocolMessageStream = new MediaProtocolMessageStream();
                channel.attachMessageStream(mediaProtocolMessageStream);
                (new CastStatusThread()).start();

                PlayerState playerState = mediaProtocolMessageStream.getPlayerState();

                if (PlayerState.PLAYING.equals(playerState)) {
                    btnPlay.setText(getString(R.string.pause) + " " + mediaProtocolMessageStream.getTitle());
                } else if (PlayerState.STOPPED.equals(playerState)) {
                    btnPlay.setText(getString(R.string.play) + " " + mediaProtocolMessageStream.getTitle());
                } else {
                    Toast.makeText(getBaseContext(), "Select media", Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onSessionStartFailed(SessionError error) {
                Log.e(TAG, "onStartFailed " + error);
                mediaProtocolMessageStream = null;
            }

            @Override
            public void onSessionEnded(SessionError error) {
                Log.i(TAG, "onEnded " + error);
                mediaProtocolMessageStream = null;
            }
        });

        try {
            applicationSession.startSession(appId);
        } catch (IOException e) {
            Log.e(TAG, "Failed to open session", e);
        }
    }

    private Medium getMediumAt(int position) {
        if (position == 0) {
            return null;
        } else if (position < dirIdx.size()) {
            int i = 1;
            Medium m = media.get(dirIdx.get(i));

            while (i < position) {
                i++;
                m = m.getMediumAt(dirIdx.get(i));
            }

            return m;
        } else {
            return null;
        }
    }

    @Override
    public List<Medium> getMediaAt(int dirPosition) {
        if (dirPosition == 0) {
            return media;
        } else {
            Medium m = getMediumAt(dirPosition);
            if (m != null) {
                return m.getDir();
            } else {
                return new ArrayList<Medium>();
            }
        }
    }

    @Override
    public Loader<List<Medium>> onCreateLoader(int arg0, Bundle arg1) {
        if (arg0 == 0) {
            return new MediaLoader(this, mediaHost);
        } else {
            return null;
        }
    }

    @Override
    public void onLoadFinished(Loader<List<Medium>> arg0, List<Medium> arg1) {
        media = arg1;
        dirIdx.clear();
        dirIdx.add(0);
        dirPagerAdapter.notifyDataSetChanged();
        viewPager.setCurrentItem(FRAGMENT_MEDIA_ROOT);
        MediaListFragment mediaListFragment = (MediaListFragment) getSupportFragmentManager()
                .findFragmentByTag(getFragmentTag(FRAGMENT_MEDIA_ROOT));
        mediaListFragment.onMediaLoaded(media);
    }

    @Override
    public void onLoaderReset(Loader<List<Medium>> arg0) {
    }

    @Override
    public void onDeviceAvailable(CastDevice device, String arg1, MediaRouteStateChangeListener listener) {
        castDevice = device;
        mediaRouteStateChangeListener = listener;
        openSession();
    }

    private String getFragmentTag(int position) {
        return "android:switcher:" + R.id.pager + ":" + position;
    }

    @Override
    public void openDir(int parent, int child) {
        int currSize = dirIdx.size() - 1;

        while (currSize > parent) {
            dirIdx.remove(currSize);
            currSize--;
        }

        dirIdx.add(child);
        dirPagerAdapter.notifyDataSetChanged();
        parent++;
        viewPager.setCurrentItem(parent, true);
        MediaListFragment mediaListFragment = (MediaListFragment) getSupportFragmentManager()
                .findFragmentByTag(getFragmentTag(parent));
        mediaListFragment.onMediaLoaded(getMediaAt(parent));
    }

    @Override
    public void openMedium(int parent, int child) {
        Medium m = getMediaAt(parent).get(child);
        final ContentMetadata contentMetadata = new ContentMetadata();
        contentMetadata.setTitle(m.getFile().substring(m.getFile().lastIndexOf(File.separator) + 1));

        if (mediaProtocolMessageStream != null) {
            String urlStr = String.format("http://%s/%s", mediaHost, m.getFile());

            try {
                URL url = new URL(urlStr);
                URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(),
                        url.getQuery(), url.getRef());
                url = uri.toURL();
                MediaProtocolCommand cmd = mediaProtocolMessageStream.loadMedia(url.toString(), contentMetadata,
                        true);
                cmd.setListener(new MediaProtocolCommand.Listener() {
                    @Override
                    public void onCompleted(MediaProtocolCommand mPCommand) {
                        btnPlay.setText(getString(R.string.pause) + " " + contentMetadata.getTitle());
                        onSetVolume(0.5);
                    }

                    @Override
                    public void onCancelled(MediaProtocolCommand mPCommand) {
                        btnPlay.setText(getString(R.string.play) + " " + contentMetadata.getTitle());
                    }
                });
            } catch (IllegalStateException e) {
                Log.e(TAG, "Problem occurred with MediaProtocolCommand during loading", e);
            } catch (IOException e) {
                Log.e(TAG, "Problem opening MediaProtocolCommand during loading", e);
            } catch (URISyntaxException e) {
                Log.e(TAG, "Problem encoding URI: " + urlStr, e);
            }
        } else {
            Toast.makeText(this, "No message stream", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_play) {
            if (castDevice != null) {
                if (mediaProtocolMessageStream != null) {
                    String text = ((Button) v).getText().toString();
                    if (text.startsWith(getString(R.string.play))) {
                        Toast.makeText(this, getString(R.string.play), Toast.LENGTH_SHORT).show();

                        try {
                            if (seekBar.getProgress() > 0) {
                                mediaProtocolMessageStream.resume();
                            } else {
                                mediaProtocolMessageStream.play();
                            }
                            btnPlay.setText(R.string.pause);
                        } catch (IllegalStateException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    } else {
                        Toast.makeText(this, getString(R.string.pause), Toast.LENGTH_SHORT).show();

                        try {
                            mediaProtocolMessageStream.stop();
                            btnPlay.setText(R.string.play);
                        } catch (IllegalStateException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            } else {
                Toast.makeText(this, getString(R.string.select_cast_device), Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    public String getTitle(int dirPosition) {
        return dirPagerAdapter.getPageTitle(dirPosition).toString();
    }

    @Override
    public void onSetVolume(double volume) {
        try {
            mediaProtocolMessageStream.setVolume(volume);
        } catch (IllegalStateException e) {
            Log.e(TAG, "Problem sending Set Volume", e);
        } catch (IOException e) {
            Log.e(TAG, "Problem sending Set Volume", e);
        }
    }

    @Override
    public void onUpdateVolume(double volumeChange) {
        RouteInfo ri = mediaRouter.getSelectedRoute();

        if (ri != null) {
            ri.requestUpdateVolume((int) (volumeChange * MAX_VOLUME_LEVEL));
        }
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        int action = event.getAction();
        int keyCode = event.getKeyCode();
        switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_UP:
            if (action == KeyEvent.ACTION_DOWN) {
                double currentVolume;

                if (mediaProtocolMessageStream != null) {
                    currentVolume = mediaProtocolMessageStream.getVolume();

                    if (currentVolume < 1.0) {
                        onSetVolume(currentVolume + VOLUME_INCREMENT);
                    }
                } else {
                    Log.e(TAG, "dispatchKeyEvent - volume up - mMPMS==null");
                }
            }

            return true;
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            if (action == KeyEvent.ACTION_DOWN) {
                double currentVolume;

                if (mediaProtocolMessageStream != null) {
                    currentVolume = mediaProtocolMessageStream.getVolume();

                    if (currentVolume > 0.0) {
                        onSetVolume(currentVolume - VOLUME_INCREMENT);
                    }
                } else {
                    Log.e(TAG, "dispatchKeyEvent - volume down - mMPMS==null");
                }
            }
            return true;
        default:
            return super.dispatchKeyEvent(event);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int itemId = item.getItemId();

        if (itemId == R.id.action_refresh) {
            if (mediaHost != null) {
                Loader<List<Medium>> loader = getSupportLoaderManager().initLoader(0, null, this);
                if (loader != null) {
                    loader.forceLoad();
                }
            }

            return true;
        } else if (itemId == android.R.id.home) {
            int tabIdx = viewPager.getCurrentItem();

            if (tabIdx > 0) {
                viewPager.setCurrentItem(--tabIdx);
            }

            return true;
        } else if (itemId == R.id.action_settings) {
            startActivityForResult(
                    new Intent(this, SettingsActivity.class).putExtra(SettingsActivity.EXTRA_APP_ID, appId)
                            .putExtra(SettingsActivity.EXTRA_HOST, mediaHost),
                    0);
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    }

    private Object isSeekingLock = new Object();
    private boolean isSeeking = false;

    @Override
    public void onStartTrackingTouch(SeekBar arg0) {
        synchronized (isSeekingLock) {
            isSeeking = true;
        }
        if (mediaProtocolMessageStream != null) {
            try {
                mediaProtocolMessageStream.stop();
            } catch (IllegalStateException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        synchronized (isSeekingLock) {
            isSeeking = false;
        }
        if (mediaProtocolMessageStream != null) {
            try {
                mediaProtocolMessageStream.playFrom(seekBar.getProgress());
            } catch (IllegalStateException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    class CastStatusThread extends Thread {

        @Override
        public void run() {
            while (mediaProtocolMessageStream != null) {
                try {
                    synchronized (isSeekingLock) {
                        if (!isSeeking) {
                            final int duration = (int) mediaProtocolMessageStream.getStreamDuration();
                            final int position = (int) mediaProtocolMessageStream.getStreamPosition();
                            MainActivity.this.runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    if (seekBar.getMax() < duration) {
                                        seekBar.setMax(duration);
                                    }

                                    if (seekBar.getProgress() != position) {
                                        seekBar.setProgress(position);
                                    }
                                }
                            });
                        }
                    }
                    Thread.sleep(1500);
                } catch (Exception e) {
                    Log.e(TAG, "Thread interrupted: " + e);
                }
            }
        }

    }

    @Override
    public String getHost() {
        return mediaHost;
    }
}