Move audio player classes to external library
This commit is contained in:
parent
155eeb49b3
commit
7b23dfeb50
|
@ -4,6 +4,7 @@ apply plugin: 'com.android.application'
|
|||
apply plugin: 'me.tatarka.retrolambda'
|
||||
|
||||
repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
|
@ -28,6 +29,7 @@ dependencies {
|
|||
compile 'com.joanzapata.iconify:android-iconify-fontawesome:2.0.3'
|
||||
compile 'com.afollestad:material-dialogs:0.7.8.0'
|
||||
|
||||
compile 'com.github.AntennaPod:AntennaPod-AudioPlayer:v1.0'
|
||||
|
||||
compile project(':core')
|
||||
compile project(':library:drag-sort-listview')
|
||||
|
|
|
@ -6,18 +6,19 @@ import android.content.Context;
|
|||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
|
||||
public class VariableSpeedDialog {
|
||||
private VariableSpeedDialog() {
|
||||
}
|
||||
|
||||
public static void showDialog(final Context context) {
|
||||
if (com.aocate.media.MediaPlayer.isPrestoLibraryInstalled(context)) {
|
||||
if (org.antennapod.audio.MediaPlayer.isPrestoLibraryInstalled(context)) {
|
||||
showSpeedSelectorDialog(context);
|
||||
} else {
|
||||
showGetPluginDialog(context);
|
||||
|
|
|
@ -460,6 +460,13 @@ public class PreferenceController {
|
|||
|
||||
ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY)
|
||||
.setEnabled(UserPreferences.isEnableAutodownload());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 16) {
|
||||
ui.findPreference(UserPreferences.PREF_SONIC).setEnabled(true);
|
||||
} else {
|
||||
Preference prefSonic = ui.findPreference(UserPreferences.PREF_SONIC);
|
||||
prefSonic.setSummary("[Android 4.1+]\n" + prefSonic.getSummary());
|
||||
}
|
||||
}
|
||||
|
||||
private void setParallelDownloadsText(int downloads) {
|
||||
|
|
|
@ -223,7 +223,15 @@
|
|||
<Preference
|
||||
android:key="prefAbout"
|
||||
android:title="@string/about_pref"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/experimental_pref">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:enabled="false"
|
||||
android:key="prefSonic"
|
||||
android:summary="@string/pref_sonic_message"
|
||||
android:title="@string/pref_sonic_title"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -32,8 +32,12 @@ android {
|
|||
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven { url "https://jitpack.io" }
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:support-v4:22.2.1'
|
||||
compile 'com.android.support:appcompat-v7:22.2.1'
|
||||
compile 'com.android.support:design:22.2.1'
|
||||
|
@ -52,4 +56,6 @@ dependencies {
|
|||
compile 'com.nineoldandroids:library:2.4.0'
|
||||
compile 'de.greenrobot:eventbus:2.4.0'
|
||||
compile 'io.reactivex:rxandroid:1.0.1'
|
||||
|
||||
compile 'com.github.AntennaPod:AntennaPod-AudioPlayer:v1.0'
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
oneway interface IDeathCallback_0_8 {
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnBufferingUpdateListenerCallback_0_8 {
|
||||
void onBufferingUpdate(int percent);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnCompletionListenerCallback_0_8 {
|
||||
void onCompletion();
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnErrorListenerCallback_0_8 {
|
||||
boolean onError(int what, int extra);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnInfoListenerCallback_0_8 {
|
||||
boolean onInfo(int what, int extra);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 {
|
||||
void onPitchAdjustmentAvailableChanged(boolean pitchAdjustmentAvailable);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnPreparedListenerCallback_0_8 {
|
||||
void onPrepared();
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnSeekCompleteListenerCallback_0_8 {
|
||||
void onSeekComplete();
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
interface IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 {
|
||||
void onSpeedAdjustmentAvailableChanged(boolean speedAdjustmentAvailable);
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.presto.service;
|
||||
|
||||
import com.aocate.presto.service.IDeathCallback_0_8;
|
||||
import com.aocate.presto.service.IOnBufferingUpdateListenerCallback_0_8;
|
||||
import com.aocate.presto.service.IOnCompletionListenerCallback_0_8;
|
||||
import com.aocate.presto.service.IOnErrorListenerCallback_0_8;
|
||||
import com.aocate.presto.service.IOnPitchAdjustmentAvailableChangedListenerCallback_0_8;
|
||||
import com.aocate.presto.service.IOnPreparedListenerCallback_0_8;
|
||||
import com.aocate.presto.service.IOnSeekCompleteListenerCallback_0_8;
|
||||
import com.aocate.presto.service.IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8;
|
||||
import com.aocate.presto.service.IOnInfoListenerCallback_0_8;
|
||||
|
||||
interface IPlayMedia_0_8 {
|
||||
boolean canSetPitch(long sessionId);
|
||||
boolean canSetSpeed(long sessionId);
|
||||
float getCurrentPitchStepsAdjustment(long sessionId);
|
||||
int getCurrentPosition(long sessionId);
|
||||
float getCurrentSpeedMultiplier(long sessionId);
|
||||
int getDuration(long sessionId);
|
||||
float getMaxSpeedMultiplier(long sessionId);
|
||||
float getMinSpeedMultiplier(long sessionId);
|
||||
int getVersionCode();
|
||||
String getVersionName();
|
||||
boolean isLooping(long sessionId);
|
||||
boolean isPlaying(long sessionId);
|
||||
void pause(long sessionId);
|
||||
void prepare(long sessionId);
|
||||
void prepareAsync(long sessionId);
|
||||
void registerOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb);
|
||||
void registerOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb);
|
||||
void registerOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb);
|
||||
void registerOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb);
|
||||
void registerOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb);
|
||||
void registerOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb);
|
||||
void registerOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb);
|
||||
void registerOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb);
|
||||
void release(long sessionId);
|
||||
void reset(long sessionId);
|
||||
void seekTo(long sessionId, int msec);
|
||||
void setAudioStreamType(long sessionId, int streamtype);
|
||||
void setDataSourceString(long sessionId, String path);
|
||||
void setDataSourceUri(long sessionId, in Uri uri);
|
||||
void setEnableSpeedAdjustment(long sessionId, boolean enableSpeedAdjustment);
|
||||
void setLooping(long sessionId, boolean looping);
|
||||
void setPitchStepsAdjustment(long sessionId, float pitchSteps);
|
||||
void setPlaybackPitch(long sessionId, float f);
|
||||
void setPlaybackSpeed(long sessionId, float f);
|
||||
void setSpeedAdjustmentAlgorithm(long sessionId, int algorithm);
|
||||
void setVolume(long sessionId, float left, float right);
|
||||
void start(long sessionId);
|
||||
long startSession(IDeathCallback_0_8 cb);
|
||||
void stop(long sessionId);
|
||||
void unregisterOnBufferingUpdateCallback(long sessionId, IOnBufferingUpdateListenerCallback_0_8 cb);
|
||||
void unregisterOnCompletionCallback(long sessionId, IOnCompletionListenerCallback_0_8 cb);
|
||||
void unregisterOnErrorCallback(long sessionId, IOnErrorListenerCallback_0_8 cb);
|
||||
void unregisterOnInfoCallback(long sessionId, IOnInfoListenerCallback_0_8 cb);
|
||||
void unregisterOnPitchAdjustmentAvailableChangedCallback(long sessionId, IOnPitchAdjustmentAvailableChangedListenerCallback_0_8 cb);
|
||||
void unregisterOnPreparedCallback(long sessionId, IOnPreparedListenerCallback_0_8 cb);
|
||||
void unregisterOnSeekCompleteCallback(long sessionId, IOnSeekCompleteListenerCallback_0_8 cb);
|
||||
void unregisterOnSpeedAdjustmentAvailableChangedCallback(long sessionId, IOnSpeedAdjustmentAvailableChangedListenerCallback_0_8 cb);
|
||||
}
|
|
@ -1,470 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.media;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AndroidMediaPlayer extends MediaPlayerImpl {
|
||||
private final static String AMP_TAG = "AocateAndroidMediaPlayer";
|
||||
|
||||
// private static final long TIMEOUT_DURATION_MS = 500;
|
||||
|
||||
android.media.MediaPlayer mp = null;
|
||||
|
||||
private android.media.MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
|
||||
public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
|
||||
if (owningMediaPlayer != null) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
if ((owningMediaPlayer.onBufferingUpdateListener != null)
|
||||
&& (owningMediaPlayer.mpi == AndroidMediaPlayer.this)) {
|
||||
owningMediaPlayer.onBufferingUpdateListener.onBufferingUpdate(owningMediaPlayer, percent);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private android.media.MediaPlayer.OnCompletionListener onCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
|
||||
public void onCompletion(android.media.MediaPlayer mp) {
|
||||
Log.d(AMP_TAG, "onCompletionListener being called");
|
||||
if (owningMediaPlayer != null) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
if (owningMediaPlayer.onCompletionListener != null) {
|
||||
owningMediaPlayer.onCompletionListener.onCompletion(owningMediaPlayer);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private android.media.MediaPlayer.OnErrorListener onErrorListener = new android.media.MediaPlayer.OnErrorListener() {
|
||||
public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
|
||||
// Once we're in errored state, any received messages are going to be junked
|
||||
if (owningMediaPlayer != null) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
if (owningMediaPlayer.onErrorListener != null) {
|
||||
return owningMediaPlayer.onErrorListener.onError(owningMediaPlayer, what, extra);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private android.media.MediaPlayer.OnInfoListener onInfoListener = new android.media.MediaPlayer.OnInfoListener() {
|
||||
public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
|
||||
if (owningMediaPlayer != null) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
if ((owningMediaPlayer.onInfoListener != null)
|
||||
&& (owningMediaPlayer.mpi == AndroidMediaPlayer.this)) {
|
||||
return owningMediaPlayer.onInfoListener.onInfo(owningMediaPlayer, what, extra);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// We have to assign this.onPreparedListener because the
|
||||
// onPreparedListener in owningMediaPlayer sets the state
|
||||
// to PREPARED. Due to prepareAsync, that's the only
|
||||
// reasonable place to do it
|
||||
// The others it just didn't make sense to have a setOnXListener that didn't use the parameter
|
||||
private android.media.MediaPlayer.OnPreparedListener onPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
|
||||
public void onPrepared(android.media.MediaPlayer mp) {
|
||||
Log.d(AMP_TAG, "Calling onPreparedListener.onPrepared()");
|
||||
if (AndroidMediaPlayer.this.owningMediaPlayer != null) {
|
||||
AndroidMediaPlayer.this.lockMuteOnPreparedCount.lock();
|
||||
try {
|
||||
if (AndroidMediaPlayer.this.muteOnPreparedCount > 0) {
|
||||
AndroidMediaPlayer.this.muteOnPreparedCount--;
|
||||
}
|
||||
else {
|
||||
AndroidMediaPlayer.this.muteOnPreparedCount = 0;
|
||||
if (AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener != null) {
|
||||
Log.d(AMP_TAG, "Invoking AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared");
|
||||
AndroidMediaPlayer.this.owningMediaPlayer.onPreparedListener.onPrepared(AndroidMediaPlayer.this.owningMediaPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
AndroidMediaPlayer.this.lockMuteOnPreparedCount.unlock();
|
||||
}
|
||||
if (owningMediaPlayer.mpi != AndroidMediaPlayer.this) {
|
||||
Log.d(AMP_TAG, "owningMediaPlayer has changed implementation");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private android.media.MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
|
||||
public void onSeekComplete(android.media.MediaPlayer mp) {
|
||||
if (owningMediaPlayer != null) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
lockMuteOnSeekCount.lock();
|
||||
try {
|
||||
if (AndroidMediaPlayer.this.muteOnSeekCount > 0) {
|
||||
AndroidMediaPlayer.this.muteOnSeekCount--;
|
||||
}
|
||||
else {
|
||||
AndroidMediaPlayer.this.muteOnSeekCount = 0;
|
||||
if (AndroidMediaPlayer.this.owningMediaPlayer.onSeekCompleteListener != null) {
|
||||
owningMediaPlayer.onSeekCompleteListener.onSeekComplete(owningMediaPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
lockMuteOnSeekCount.unlock();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public AndroidMediaPlayer(com.aocate.media.MediaPlayer owningMediaPlayer, Context context) {
|
||||
super(owningMediaPlayer, context);
|
||||
|
||||
mp = new MediaPlayer();
|
||||
|
||||
// final ReentrantLock lock = new ReentrantLock();
|
||||
// Handler handler = new Handler(Looper.getMainLooper()) {
|
||||
// @Override
|
||||
// public void handleMessage(Message msg) {
|
||||
// Log.d(AMP_TAG, "Instantiating new AndroidMediaPlayer from Handler");
|
||||
// lock.lock();
|
||||
// if (mp == null) {
|
||||
// mp = new MediaPlayer();
|
||||
// }
|
||||
// lock.unlock();
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// long endTime = System.currentTimeMillis() + TIMEOUT_DURATION_MS;
|
||||
//
|
||||
// while (true) {
|
||||
// // Retry messages until mp isn't null or it's time to give up
|
||||
// handler.sendMessage(handler.obtainMessage());
|
||||
// if ((mp != null)
|
||||
// || (endTime < System.currentTimeMillis())) {
|
||||
// break;
|
||||
// }
|
||||
// try {
|
||||
// Thread.sleep(50);
|
||||
// } catch (InterruptedException e) {
|
||||
// // TODO Auto-generated catch block
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
|
||||
if (mp == null) {
|
||||
throw new IllegalStateException("Did not instantiate android.media.MediaPlayer successfully");
|
||||
}
|
||||
|
||||
mp.setOnBufferingUpdateListener(this.onBufferingUpdateListener);
|
||||
mp.setOnCompletionListener(this.onCompletionListener);
|
||||
mp.setOnErrorListener(this.onErrorListener);
|
||||
mp.setOnInfoListener(this.onInfoListener);
|
||||
Log.d(AMP_TAG, " ++++++++++++++++++++++++++++++++ Setting prepared listener to this.onPreparedListener");
|
||||
mp.setOnPreparedListener(this.onPreparedListener);
|
||||
mp.setOnSeekCompleteListener(this.onSeekCompleteListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSetPitch() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSetSpeed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getCurrentPitchStepsAdjustment() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPosition() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
return mp.getCurrentPosition();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getCurrentSpeedMultiplier() {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDuration() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
return mp.getDuration();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxSpeedMultiplier() {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinSpeedMultiplier() {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLooping() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
return mp.isLooping();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
return mp.isPlaying();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.pause();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare() throws IllegalStateException, IOException {
|
||||
owningMediaPlayer.lock.lock();
|
||||
Log.d(AMP_TAG, "prepare()");
|
||||
try {
|
||||
mp.prepare();
|
||||
Log.d(AMP_TAG, "Finish prepare()");
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareAsync() {
|
||||
mp.prepareAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
if (mp != null) {
|
||||
Log.d(AMP_TAG, "mp.release()");
|
||||
mp.release();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.reset();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int msec) throws IllegalStateException {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.setOnSeekCompleteListener(this.onSeekCompleteListener);
|
||||
mp.seekTo(msec);
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAudioStreamType(int streamtype) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.setAudioStreamType(streamtype);
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDataSource(Context context, Uri uri)
|
||||
throws IllegalArgumentException, IllegalStateException, IOException {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
Log.d(AMP_TAG, "setDataSource(context, " + uri.toString() + ")");
|
||||
mp.setDataSource(context, uri);
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDataSource(String path) throws IllegalArgumentException,
|
||||
IllegalStateException, IOException {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
Log.d(AMP_TAG, "setDataSource(" + path + ")");
|
||||
mp.setDataSource(path);
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) {
|
||||
// Can't!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLooping(boolean loop) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.setLooping(loop);
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPitchStepsAdjustment(float pitchSteps) {
|
||||
// Can't!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaybackPitch(float f) {
|
||||
// Can't!
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaybackSpeed(float f) {
|
||||
// Can't!
|
||||
Log.d(AMP_TAG, "setPlaybackSpeed(" + f + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSpeedAdjustmentAlgorithm(int algorithm) {
|
||||
// Can't!
|
||||
Log.d(AMP_TAG, "setSpeedAdjustmentAlgorithm(" + algorithm + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume(float leftVolume, float rightVolume) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.setVolume(leftVolume, rightVolume);
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWakeMode(Context context, int mode) {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
if (mode != 0) {
|
||||
mp.setWakeMode(context, mode);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.start();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
owningMediaPlayer.lock.lock();
|
||||
try {
|
||||
mp.stop();
|
||||
}
|
||||
finally {
|
||||
owningMediaPlayer.lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,118 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.media;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
public abstract class MediaPlayerImpl {
|
||||
private static final String MPI_TAG = "AocateMediaPlayerImpl";
|
||||
protected final MediaPlayer owningMediaPlayer;
|
||||
protected final Context mContext;
|
||||
protected int muteOnPreparedCount = 0;
|
||||
protected int muteOnSeekCount = 0;
|
||||
|
||||
public MediaPlayerImpl(MediaPlayer owningMediaPlayer, Context context) {
|
||||
this.owningMediaPlayer = owningMediaPlayer;
|
||||
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public abstract boolean canSetPitch();
|
||||
|
||||
public abstract boolean canSetSpeed();
|
||||
|
||||
public abstract float getCurrentPitchStepsAdjustment();
|
||||
|
||||
public abstract int getCurrentPosition();
|
||||
|
||||
public abstract float getCurrentSpeedMultiplier();
|
||||
|
||||
public abstract int getDuration();
|
||||
|
||||
public abstract float getMaxSpeedMultiplier();
|
||||
|
||||
public abstract float getMinSpeedMultiplier();
|
||||
|
||||
public abstract boolean isLooping();
|
||||
|
||||
public abstract boolean isPlaying();
|
||||
|
||||
public abstract void pause();
|
||||
|
||||
public abstract void prepare() throws IllegalStateException, IOException;
|
||||
|
||||
public abstract void prepareAsync();
|
||||
|
||||
public abstract void release();
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
public abstract void seekTo(int msec) throws IllegalStateException;
|
||||
|
||||
public abstract void setAudioStreamType(int streamtype);
|
||||
|
||||
public abstract void setDataSource(Context context, Uri uri) throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
|
||||
public abstract void setDataSource(String path) throws IllegalArgumentException, IllegalStateException, IOException;
|
||||
|
||||
public abstract void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
|
||||
|
||||
public abstract void setLooping(boolean loop);
|
||||
|
||||
public abstract void setPitchStepsAdjustment(float pitchSteps);
|
||||
|
||||
public abstract void setPlaybackPitch(float f);
|
||||
|
||||
public abstract void setPlaybackSpeed(float f);
|
||||
|
||||
public abstract void setSpeedAdjustmentAlgorithm(int algorithm);
|
||||
|
||||
public abstract void setVolume(float leftVolume, float rightVolume);
|
||||
|
||||
public abstract void setWakeMode(Context context, int mode);
|
||||
|
||||
public abstract void start();
|
||||
|
||||
public abstract void stop();
|
||||
|
||||
protected ReentrantLock lockMuteOnPreparedCount = new ReentrantLock();
|
||||
public void muteNextOnPrepare() {
|
||||
lockMuteOnPreparedCount.lock();
|
||||
Log.d(MPI_TAG, "muteNextOnPrepare()");
|
||||
try {
|
||||
this.muteOnPreparedCount++;
|
||||
}
|
||||
finally {
|
||||
lockMuteOnPreparedCount.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected ReentrantLock lockMuteOnSeekCount = new ReentrantLock();
|
||||
public void muteNextSeek() {
|
||||
lockMuteOnSeekCount.lock();
|
||||
Log.d(MPI_TAG, "muteNextOnSeek()");
|
||||
try {
|
||||
this.muteOnSeekCount++;
|
||||
}
|
||||
finally {
|
||||
lockMuteOnSeekCount.unlock();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,31 +0,0 @@
|
|||
// Copyright 2011, Aocate, Inc.
|
||||
//
|
||||
// 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 com.aocate.media;
|
||||
|
||||
public class SpeedAdjustmentAlgorithm {
|
||||
/**
|
||||
* Use this to use the user-specified algorithm
|
||||
*/
|
||||
public static int DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Better for voice audio
|
||||
*/
|
||||
public static int SONIC = 1;
|
||||
/**
|
||||
* Better for music audio
|
||||
*/
|
||||
public static int WSOLA = 2;
|
||||
}
|
|
@ -89,6 +89,10 @@ public class UserPreferences {
|
|||
public static final String IMAGE_CACHE_DEFAULT_VALUE = "100";
|
||||
public static final int IMAGE_CACHE_SIZE_MINIMUM = 20;
|
||||
|
||||
// Experimental
|
||||
public static final String PREF_SONIC = "prefSonic";
|
||||
public static final String PREF_NORMALIZER = "prefNormalizer";
|
||||
|
||||
// Constants
|
||||
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
|
||||
public static int FEED_ORDER_COUNTER = 0;
|
||||
|
@ -469,6 +473,9 @@ public class UserPreferences {
|
|||
return selectedSpeeds;
|
||||
}
|
||||
|
||||
public static boolean useSonic() {
|
||||
return prefs.getBoolean(PREF_SONIC, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the folder where the app stores all of its data. This method will
|
||||
|
|
|
@ -1047,9 +1047,9 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
|
|||
return mp;
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
|
||||
private final org.antennapod.audio.MediaPlayer.OnCompletionListener audioCompletionListener = new org.antennapod.audio.MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(com.aocate.media.MediaPlayer mp) {
|
||||
public void onCompletion(org.antennapod.audio.MediaPlayer mp) {
|
||||
genericOnCompletion();
|
||||
}
|
||||
};
|
||||
|
@ -1065,9 +1065,9 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
|
|||
endPlayback();
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
|
||||
private final org.antennapod.audio.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new org.antennapod.audio.MediaPlayer.OnBufferingUpdateListener() {
|
||||
@Override
|
||||
public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
|
||||
public void onBufferingUpdate(org.antennapod.audio.MediaPlayer mp,
|
||||
int percent) {
|
||||
genericOnBufferingUpdate(percent);
|
||||
}
|
||||
|
@ -1084,9 +1084,9 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
|
|||
callback.onBufferingUpdate(percent);
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
|
||||
private final org.antennapod.audio.MediaPlayer.OnInfoListener audioInfoListener = new org.antennapod.audio.MediaPlayer.OnInfoListener() {
|
||||
@Override
|
||||
public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
|
||||
public boolean onInfo(org.antennapod.audio.MediaPlayer mp, int what,
|
||||
int extra) {
|
||||
return genericInfoListener(what);
|
||||
}
|
||||
|
@ -1103,9 +1103,9 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
|
|||
return callback.onMediaPlayerInfo(what);
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
|
||||
private final org.antennapod.audio.MediaPlayer.OnErrorListener audioErrorListener = new org.antennapod.audio.MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(com.aocate.media.MediaPlayer mp, int what,
|
||||
public boolean onError(org.antennapod.audio.MediaPlayer mp, int what,
|
||||
int extra) {
|
||||
return genericOnError(mp, what, extra);
|
||||
}
|
||||
|
@ -1122,9 +1122,9 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
|
|||
return callback.onMediaPlayerError(inObj, what, extra);
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
|
||||
private final org.antennapod.audio.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new org.antennapod.audio.MediaPlayer.OnSeekCompleteListener() {
|
||||
@Override
|
||||
public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
|
||||
public void onSeekComplete(org.antennapod.audio.MediaPlayer mp) {
|
||||
genericSeekCompleteListener();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,7 +3,9 @@ package de.danoeh.antennapod.core.util.playback;
|
|||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceHolder;
|
||||
import com.aocate.media.MediaPlayer;
|
||||
import org.antennapod.audio.MediaPlayer;
|
||||
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
|
||||
public class AudioPlayer extends MediaPlayer implements IPlayer {
|
||||
private static final String TAG = "AudioPlayer";
|
||||
|
@ -16,7 +18,6 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
|
|||
public void setScreenOnWhilePlaying(boolean screenOn) {
|
||||
Log.e(TAG, "Setting screen on while playing not supported in Audio Player");
|
||||
throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -31,4 +32,9 @@ public class AudioPlayer extends MediaPlayer implements IPlayer {
|
|||
public void setVideoScalingMode(int mode) {
|
||||
throw new UnsupportedOperationException("Setting scaling mode is not supported in Audio Player");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useSonic() {
|
||||
return UserPreferences.useSonic();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -344,6 +344,9 @@
|
|||
<string name="pref_smart_mark_as_played_disabled">Disabled</string>
|
||||
<string name="pref_image_cache_size_title">Image Cache Size</string>
|
||||
<string name="pref_image_cache_size_sum">Size of the disk cache for images.</string>
|
||||
<string name="experimental_pref">Experimental</string>
|
||||
<string name="pref_sonic_title">Sonic media player</string>
|
||||
<string name="pref_sonic_message">Use built-in sonic media player as a replacement for Prestissimo</string>
|
||||
|
||||
<!-- Auto-Flattr dialog -->
|
||||
<string name="auto_flattr_enable">Enable automatic flattring</string>
|
||||
|
|
Loading…
Reference in New Issue