Implemented MediaProgressIndicator widget to allow for code sharing (#405)

* Refactored NowPlayingFragment to use the new MediaProgressIndicator
This commit is contained in:
Martijn Brekhof 2017-06-20 20:05:27 +02:00 committed by Synced Synapse
parent 7ce2c014e4
commit 134ec550e8
6 changed files with 226 additions and 125 deletions

View File

@ -283,6 +283,19 @@ public class PlayerType {
this.milliseconds = milliseconds;
}
/**
* Converts second duration into a hour, minutes, seconds duration
* @param seconds
*/
public PositionTime(int seconds) {
this.hours = seconds / 3600;
int remainder = seconds - (this.hours * 3600);
this.minutes = remainder / 60;
remainder = remainder - (this.minutes * 60);
this.seconds = remainder;
this.milliseconds = 0;
}
public JsonNode toJsonNode() {
final ObjectNode node = objectMapper.createObjectNode();
node.put(HOURS, hours);

View File

@ -56,6 +56,7 @@ import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.jsonrpc.type.VideoType;
import org.xbmc.kore.ui.generic.GenericSelectDialog;
import org.xbmc.kore.ui.sections.video.AllCastActivity;
import org.xbmc.kore.ui.widgets.MediaProgressIndicator;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
@ -73,7 +74,8 @@ import butterknife.OnClick;
public class NowPlayingFragment extends Fragment
implements HostConnectionObserver.PlayerEventsObserver,
HostConnectionObserver.ApplicationEventsObserver,
GenericSelectDialog.GenericSelectDialogListener {
GenericSelectDialog.GenericSelectDialogListener,
MediaProgressIndicator.OnProgressChangeListener {
private static final String TAG = LogUtils.makeLogTag(NowPlayingFragment.class);
/**
@ -149,9 +151,7 @@ public class NowPlayingFragment extends Fragment
@InjectView(R.id.media_title) TextView mediaTitle;
@InjectView(R.id.media_undertitle) TextView mediaUndertitle;
@InjectView(R.id.media_duration) TextView mediaDuration;
@InjectView(R.id.media_progress) TextView mediaProgress;
@InjectView(R.id.seek_bar) SeekBar mediaSeekbar;
@InjectView(R.id.progress_info) MediaProgressIndicator mediaProgressIndicator;
@InjectView(R.id.volume_bar) SeekBar volumeSeekBar;
@InjectView(R.id.volume_text) TextView volumeTextView;
@ -538,6 +538,23 @@ public class NowPlayingFragment extends Fragment
nowPlayingListener.SwitchToRemotePanel();
}
@Override
public void onProgressChanged(int progress) {
PlayerType.PositionTime positionTime = new PlayerType.PositionTime(progress);
Player.Seek seekAction = new Player.Seek(currentActivePlayerId, positionTime);
seekAction.execute(hostManager.getConnection(), new ApiCallback<PlayerType.SeekReturnType>() {
@Override
public void onSuccess(PlayerType.SeekReturnType result) {
// Ignore
}
@Override
public void onError(int errorCode, String description) {
LogUtils.LOGD(TAG, "Got an error calling Player.Seek. Error code: " + errorCode + ", description: " + description);
}
}, callbackHandler);
}
/**
* HostConnectionObserver.PlayerEventsObserver interface callbacks
*/
@ -716,8 +733,15 @@ public class NowPlayingFragment extends Fragment
mediaTitle.setText(title);
mediaUndertitle.setText(underTitle);
setDurationInfo(getItemResult.type, getPropertiesResult.time, getPropertiesResult.totaltime, getPropertiesResult.speed);
mediaSeekbar.setOnSeekBarChangeListener(seekbarChangeListener);
mediaProgressIndicator.setOnProgressChangeListener(this);
mediaProgressIndicator.setMaxProgress(getPropertiesResult.totaltime.ToSeconds());
mediaProgressIndicator.setProgress(getPropertiesResult.time.ToSeconds());
int speed = getPropertiesResult.speed;
//TODO: check if following is still necessary for PVR playback
if (getItemResult.type.equals(ListType.ItemsAll.TYPE_CHANNEL))
speed = 1;
mediaProgressIndicator.setSpeed(speed);
if (!TextUtils.isEmpty(year) || !TextUtils.isEmpty(genreSeason)) {
mediaYear.setVisibility(View.VISIBLE);
@ -855,7 +879,8 @@ public class NowPlayingFragment extends Fragment
*/
private void stopNowPlayingInfo() {
// Just stop the seek bar handler callbacks
mediaSeekbar.removeCallbacks(seekBarUpdater);
mediaProgressIndicator.setSpeed(0);
availableSubtitles = null;
availableAudioStreams = null;
currentSubtitleIndex = -1;
@ -896,56 +921,6 @@ public class NowPlayingFragment extends Fragment
mediaCurrentTime = 0; // s
private static final int SEEK_BAR_UPDATE_INTERVAL = 1000; // ms
/**
* Seek bar runnable updater. Runs once a second
*/
private Runnable seekBarUpdater = new Runnable() {
@Override
public void run() {
if ((mediaTotalTime == 0) || (mediaCurrentTime >= mediaTotalTime)) {
mediaSeekbar.removeCallbacks(this);
return;
}
mediaCurrentTime += 1;
mediaSeekbar.setProgress(mediaCurrentTime);
int hours = mediaCurrentTime / 3600;
int minutes = (mediaCurrentTime % 3600) / 60;
int seconds = (mediaCurrentTime % 3600) % 60;
mediaProgress.setText(UIUtils.formatTime(hours, minutes, seconds));
mediaSeekbar.postDelayed(this, SEEK_BAR_UPDATE_INTERVAL);
}
};
/**
* Sets the information about current media duration and sets seekbar
* @param type What is playing
* @param time Current time
* @param totalTime Total time
* @param speed Media speed
*/
private void setDurationInfo(String type, GlobalType.Time time, GlobalType.Time totalTime, int speed) {
mediaTotalTime = totalTime.hours * 3600 +
totalTime.minutes * 60 +
totalTime.seconds;
mediaSeekbar.setMax(mediaTotalTime);
mediaDuration.setText(UIUtils.formatTime(totalTime));
mediaCurrentTime = time.hours * 3600 +
time.minutes * 60 +
time.seconds;
mediaSeekbar.setProgress(mediaCurrentTime);
mediaProgress.setText(UIUtils.formatTime(time));
// Only update when its playing
mediaSeekbar.removeCallbacks(seekBarUpdater);
if ((speed == 1) || (type.equals(ListType.ItemsAll.TYPE_CHANNEL))) {
mediaSeekbar.postDelayed(seekBarUpdater, SEEK_BAR_UPDATE_INTERVAL);
}
}
/**
* Sets UI volume state
* @param muted whether volume is muted
@ -993,51 +968,4 @@ public class NowPlayingFragment extends Fragment
}
};
/**
* Seekbar change listener. Sends seek commands to XBMC based on the seekbar position
*/
private SeekBar.OnSeekBarChangeListener seekbarChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
mediaCurrentTime = progress;
int hours = mediaCurrentTime / 3600;
int minutes = (mediaCurrentTime % 3600) / 60;
int seconds = (mediaCurrentTime % 3600) % 60;
mediaProgress.setText(UIUtils.formatTime(hours, minutes, seconds));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Stop the seekbar updating
seekBar.removeCallbacks(seekBarUpdater);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int hours = mediaCurrentTime / 3600;
int minutes = (mediaCurrentTime % 3600) / 60;
int seconds = (mediaCurrentTime % 3600) % 60;
PlayerType.PositionTime positionTime = new PlayerType.PositionTime(hours, minutes, seconds, 0);
Player.Seek seekAction = new Player.Seek(currentActivePlayerId, positionTime);
seekAction.execute(hostManager.getConnection(), new ApiCallback<PlayerType.SeekReturnType>() {
@Override
public void onSuccess(PlayerType.SeekReturnType result) {
// Ignore
}
@Override
public void onError(int errorCode, String description) {
LogUtils.LOGD(TAG, "Got an error calling Player.Seek. Error code: " + errorCode + ", description: " + description);
}
}, callbackHandler);
// Reset the updating
seekBar.postDelayed(seekBarUpdater, 1000);
}
};
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2017 Martijn Brekhof. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xbmc.kore.ui.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import org.xbmc.kore.R;
import org.xbmc.kore.utils.UIUtils;
public class MediaProgressIndicator extends LinearLayout {
private SeekBar seekBar;
private TextView durationTextView;
private TextView progressTextView;
private int speed = 0;
private int maxProgress;
private int progress;
private static final int SEEK_BAR_UPDATE_INTERVAL = 1000; // ms
private int progressIncrement;
private OnProgressChangeListener onProgressChangeListener;
public interface OnProgressChangeListener {
void onProgressChanged(int progress);
}
public MediaProgressIndicator(Context context) {
super(context);
initializeView(context);
}
public MediaProgressIndicator(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
initializeView(context);
}
private void initializeView(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.media_progress_indicator, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
seekBar = (SeekBar) findViewById(R.id.mpi_seek_bar);
progressTextView = (TextView) findViewById(R.id.mpi_progress);
durationTextView = (TextView) findViewById(R.id.mpi_duration);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
MediaProgressIndicator.this.progress = progress;
progressTextView.setText(UIUtils.formatTime(MediaProgressIndicator.this.progress));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// Stop the seekbar from updating
seekBar.removeCallbacks(seekBarUpdater);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (onProgressChangeListener != null)
onProgressChangeListener.onProgressChanged(seekBar.getProgress());
if (speed > 0)
seekBar.postDelayed(seekBarUpdater, SEEK_BAR_UPDATE_INTERVAL);
}
});
}
private Runnable seekBarUpdater = new Runnable() {
@Override
public void run() {
if ((maxProgress == 0) || (progress >= maxProgress)) {
seekBar.removeCallbacks(this);
return;
}
progress += progressIncrement;
setProgress(progress);
seekBar.postDelayed(this, SEEK_BAR_UPDATE_INTERVAL);
}
};
public void setOnProgressChangeListener(OnProgressChangeListener onProgressChangeListener) {
this.onProgressChangeListener = onProgressChangeListener;
}
public void setProgress(int progress) {
this.progress = progress;
seekBar.setProgress(progress);
progressTextView.setText(UIUtils.formatTime(progress));
}
public void setMaxProgress(int max) {
maxProgress = max;
seekBar.setMax(max);
durationTextView.setText(UIUtils.formatTime(max));
}
/**
* Sets the play speed for the progress indicator
* @param speed set to 0 to stop updating the progress indicator.
*/
public void setSpeed(int speed) {
if( speed == this.speed )
return;
this.speed = speed;
this.progressIncrement = speed * (SEEK_BAR_UPDATE_INTERVAL/1000);
seekBar.removeCallbacks(seekBarUpdater);
if (speed > 0)
seekBar.postDelayed(seekBarUpdater, SEEK_BAR_UPDATE_INTERVAL);
}
}

View File

@ -81,7 +81,7 @@
android:scaleType="centerCrop"/>
<!-- Progress bar -->
<LinearLayout
<org.xbmc.kore.ui.widgets.MediaProgressIndicator
android:id="@+id/progress_info"
android:layout_below="@id/media_undertitle"
android:layout_width="match_parent"
@ -90,26 +90,7 @@
android:paddingRight="@dimen/small_padding"
android:paddingLeft="@dimen/small_padding"
android:orientation="horizontal"
android:background="?attr/contentBackgroundColor">
<TextView
android:id="@+id/media_progress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
style="@style/TextAppearance.Media.Progress"/>
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_margin="4dp"/>
<TextView
android:id="@+id/media_duration"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
style="@style/TextAppearance.Media.Progress"/>
</LinearLayout>
android:background="?attr/contentBackgroundColor"/>
<!-- Media control buttons -->
<LinearLayout

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Martijn Brekhof. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/mpi_progress"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/zeroprogress"/>
<SeekBar
android:id="@+id/mpi_seek_bar"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:layout_margin="4dp"/>
<TextView
android:id="@+id/mpi_duration"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"/>
</merge>

View File

@ -416,5 +416,6 @@
<string name="Refreshing_not_implemented_for_this_item">Refreshing not implemented for this item</string>
<string name="no_songs_found_refresh">No songs found\\n\\nSwipe down to refresh</string>
<string name="not_implemented">Not implemented</string>
<string name="zeroprogress">0:00</string>
</resources>