/* Viewer for Khan Academy Copyright (C) 2012 Concentric Sky, Inc. 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/>. */ package com.concentricsky.android.khanacademy.views; import java.util.Formatter; import java.util.Locale; import android.content.Context; import android.media.MediaPlayer; import android.util.AttributeSet; import android.view.View; import android.widget.ImageButton; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.VideoView; import com.concentricsky.android.khan.R; import com.concentricsky.android.khanacademy.util.Log; public class VideoController extends RelativeLayout implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener { public static final String LOG_TAG = VideoController.class.getSimpleName(); // constants for use with hideAfter / showUntil public static final int DEFAULT_SHOW_DURATION = 3000; public static final int NEVER = -1; public static final int NOW = 0; private StringBuilder stringBuilder; private Formatter stringFormatter; private TextView time; private ImageButton button; private View buttonContainer; private ImageButton fullscreenButton; private SeekBar seekBar; private VideoView video; private int duration; private Callbacks callbacks; public interface Callbacks { public void onVideoStarted(); public void onVideoStopped(); public void onVideoCompleted(); } public void setCallbacks(Callbacks callbacks) { this.callbacks = callbacks; } private void doVideoStarted() { if (callbacks != null) { callbacks.onVideoStarted(); } } private void doVideoStopped() { if (callbacks != null) { callbacks.onVideoStopped(); } } private void doVideoCompleted() { if (callbacks != null) { callbacks.onVideoCompleted(); } } private FullscreenRequestHandler fullscreenHandler; public interface FullscreenRequestHandler { public void onFullscreenToggleRequested(); } public void setFullscreenRequestHandler(FullscreenRequestHandler handler) { fullscreenHandler = handler; } private void doFullscreenToggleRequested() { if (fullscreenHandler != null) { fullscreenHandler.onFullscreenToggleRequested(); } } private SeekBar.OnSeekBarChangeListener seekBarListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser && video != null) { updateTime(progress); video.seekTo(progress); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { showUntil(NEVER); } @Override public void onStopTrackingTouch(SeekBar seekBar) { int dur = video != null && video.isPlaying() ? DEFAULT_SHOW_DURATION : NEVER; showUntil(dur); } }; public VideoController(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public VideoController(Context context, AttributeSet attrs) { super(context, attrs); init(); } public VideoController(Context context) { super(context); init(); } private void init() { } @Override protected void onFinishInflate() { super.onFinishInflate(); time = (TextView) findViewById(R.id.controller_time); button = (ImageButton) findViewById(R.id.controller_button); buttonContainer = findViewById(R.id.controller_button_container); fullscreenButton = (ImageButton) findViewById(R.id.fullscreen_button); seekBar = (SeekBar) findViewById(R.id.controller_seek_bar); seekBar.setOnSeekBarChangeListener(seekBarListener); seekBar.setMax(0); seekBar.setProgress(0); seekBar.setSecondaryProgress(0); duration = 0; updateTime(0); showPlayButton(); } public void setVideoView(VideoView video) { this.video = video; } public void play() { if (video != null) { video.start(); doVideoStarted(); showPauseButton(); startTrackingVideo(); // showUntil(DEFAULT_SHOW_DURATION); } } public void pause() { if (video != null) { video.pause(); doVideoStopped(); showPlayButton(); stopTrackingVideo(); // showUntil(NEVER); } } public void togglePlay() { if (video != null) { if (video.isPlaying()) { pause(); } else { play(); } } } private void showPlayButton() { button.setBackgroundResource(R.drawable.av_play_over_video); } private void showPauseButton() { button.setBackgroundResource(R.drawable.av_pause_over_video); } private void showFullscreenButton() { if (fullscreenButton != null) { fullscreenButton.setBackgroundResource(R.drawable.av_full_screen); } } private void showFullscreenExitButton() { if (fullscreenButton != null) { fullscreenButton.setBackgroundResource(R.drawable.av_return_from_full_screen); } } public void setFullscreen(boolean isFullscreen) { if (isFullscreen) { showFullscreenExitButton(); } else { showFullscreenButton(); } } public void updateBar() { Log.d(LOG_TAG, "updateBar"); if (video != null) { int position = video.getCurrentPosition(); seekBar.setProgress(position); seekBar.setSecondaryProgress((int) (video.getBufferPercentage() / 100.0 * duration)); updateTime(position); } } private Runnable updateBarRunnable = new Runnable() { @Override public void run() { updateBar(); if (video != null && video.isPlaying()) { postDelayed(updateBarRunnable, 1000); } } }; private void startTrackingVideo() { post(updateBarRunnable); } private void stopTrackingVideo() { removeCallbacks(updateBarRunnable); } private String stringForTime(int timeMs) { int totalSeconds = timeMs / 1000; int seconds = totalSeconds % 60; int minutes = (totalSeconds / 60) % 60; int hours = totalSeconds / 3600; if (stringBuilder == null) { stringBuilder = new StringBuilder(); } if (stringFormatter == null) { stringFormatter = new Formatter(stringBuilder, Locale.getDefault()); } stringBuilder.setLength(0); if (hours > 0) { return stringFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); } else { return stringFormatter.format("%02d:%02d", minutes, seconds).toString(); } } private void updateTime(int position) { time.setText(stringForTime(position)); } @Override protected void onDetachedFromWindow() { Log.d(LOG_TAG, "onDetachedFromWindow"); super.onDetachedFromWindow(); removeCallbacks(updateBarRunnable); removeCallbacks(hideRunnable); seekBar.setOnSeekBarChangeListener(null); buttonContainer.setOnClickListener(null); if (fullscreenButton != null) { fullscreenButton.setOnClickListener(null); } updateBarRunnable = null; hideRunnable = null; seekBarListener = null; buttonListener = null; fullscreenButtonListener = null; } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { Log.d(LOG_TAG, "onWindowFocusChanged: " + hasWindowFocus); super.onWindowFocusChanged(hasWindowFocus); if (hasWindowFocus) { startTrackingVideo(); } else { stopTrackingVideo(); removeCallbacks(hideRunnable); } } @Override public void onCompletion(MediaPlayer mp) { updateBar(); showPlayButton(); stopTrackingVideo(); doVideoStopped(); doVideoCompleted(); // showUntil(NEVER); } private View.OnClickListener buttonListener = new View.OnClickListener() { @Override public void onClick(View v) { togglePlay(); } }; private View.OnClickListener fullscreenButtonListener = new View.OnClickListener() { @Override public void onClick(View v) { doFullscreenToggleRequested(); } }; @Override public void onPrepared(MediaPlayer mp) { if (video != null) { duration = video.getDuration(); buttonContainer.setOnClickListener(buttonListener); if (fullscreenButton != null) { fullscreenButton.setOnClickListener(fullscreenButtonListener); } seekBar.setMax(duration); updateBar(); } } public void show() { setVisibility(View.VISIBLE); } public void showUntil(int ms) { show(); hideAfter(ms); } public void hideAfter(int ms) { removeCallbacks(hideRunnable); if (ms != NEVER) { postDelayed(hideRunnable, ms); } } public void hide() { setVisibility(View.GONE); } private Runnable hideRunnable = new Runnable() { @Override public void run() { Log.d(LOG_TAG, "hideRunnable"); hide(); } }; }