diff --git a/PLDroidPlayerDemo/.gitignore b/PLDroidPlayerDemo/.gitignore index c6cbe56..afbdab3 100644 --- a/PLDroidPlayerDemo/.gitignore +++ b/PLDroidPlayerDemo/.gitignore @@ -1,8 +1,6 @@ -*.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build -/captures diff --git a/PLDroidPlayerDemo/app/build.gradle b/PLDroidPlayerDemo/app/build.gradle index 0398340..f65a892 100644 --- a/PLDroidPlayerDemo/app/build.gradle +++ b/PLDroidPlayerDemo/app/build.gradle @@ -1,13 +1,13 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion "24.0.0 rc1" + compileSdkVersion 22 + buildToolsVersion "22.0.1" defaultConfig { - applicationId "com.pili.pldroid.playerdemo" + applicationId "com.pili.android.playerdemo" minSdkVersion 9 - targetSdkVersion 23 + targetSdkVersion 22 versionCode 1 versionName "1.0" } @@ -20,6 +20,8 @@ android { } dependencies { - compile 'com.android.support:design:23.2.1' - compile files('libs/pldroid-player-1.2.0.jar') + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:22.0.0' + compile files('libs/ijkmediaplayer.jar') + compile files('libs/pldroid-player-1.1.6.jar') } diff --git a/PLDroidPlayerDemo/app/libs/ijkmediaplayer.jar b/PLDroidPlayerDemo/app/libs/ijkmediaplayer.jar new file mode 100644 index 0000000..3253d75 Binary files /dev/null and b/PLDroidPlayerDemo/app/libs/ijkmediaplayer.jar differ diff --git a/PLDroidPlayerDemo/app/libs/pldroid-player-1.1.6.jar b/PLDroidPlayerDemo/app/libs/pldroid-player-1.1.6.jar new file mode 100644 index 0000000..68e1bb5 Binary files /dev/null and b/PLDroidPlayerDemo/app/libs/pldroid-player-1.1.6.jar differ diff --git a/PLDroidPlayerDemo/app/libs/pldroid-player-1.2.0.jar b/PLDroidPlayerDemo/app/libs/pldroid-player-1.2.0.jar deleted file mode 100644 index 474ddb6..0000000 Binary files a/PLDroidPlayerDemo/app/libs/pldroid-player-1.2.0.jar and /dev/null differ diff --git a/PLDroidPlayerDemo/app/proguard-rules.pro b/PLDroidPlayerDemo/app/proguard-rules.pro index 6bbda03..ba46e93 100644 --- a/PLDroidPlayerDemo/app/proguard-rules.pro +++ b/PLDroidPlayerDemo/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified -# in /Users/lujun/Library/Android/sdk/tools/proguard/proguard-android.txt +# in /Users/jerikc/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # @@ -15,5 +15,3 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} - --keep class com.pili.pldroid.player.** { *; } \ No newline at end of file diff --git a/PLDroidPlayerDemo/app/src/androidTest/java/com/pili/pldroid/playerdemo/ApplicationTest.java b/PLDroidPlayerDemo/app/src/androidTest/java/com/pili/pldroid/playerdemo/ApplicationTest.java new file mode 100644 index 0000000..b6dedd5 --- /dev/null +++ b/PLDroidPlayerDemo/app/src/androidTest/java/com/pili/pldroid/playerdemo/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.pili.pldroid.playerdemo; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/PLDroidPlayerDemo/app/src/main/AndroidManifest.xml b/PLDroidPlayerDemo/app/src/main/AndroidManifest.xml index cdacbcc..115852c 100644 --- a/PLDroidPlayerDemo/app/src/main/AndroidManifest.xml +++ b/PLDroidPlayerDemo/app/src/main/AndroidManifest.xml @@ -3,32 +3,46 @@ package="com.pili.pldroid.playerdemo" > - + + + + android:theme="@style/AppTheme" > + android:name=".MainActivity" + android:label="@string/app_name" > + - - - - - - + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/AudioPlayerActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/AudioPlayerActivity.java new file mode 100644 index 0000000..bf09459 --- /dev/null +++ b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/AudioPlayerActivity.java @@ -0,0 +1,197 @@ +package com.pili.pldroid.playerdemo; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; + +import com.pili.pldroid.player.AVOptions; +import com.pili.pldroid.player.AudioPlayer; +import com.pili.pldroid.player.PlayerCode; +import com.pili.pldroid.playerdemo.R; +import com.pili.pldroid.playerdemo.common.Util; +import com.pili.pldroid.playerdemo.widget.MediaController; + +import tv.danmaku.ijk.media.player.IMediaPlayer; +import tv.danmaku.ijk.media.player.IjkMediaPlayer; + +public class AudioPlayerActivity extends Activity implements + IjkMediaPlayer.OnCompletionListener, + IjkMediaPlayer.OnInfoListener, + IjkMediaPlayer.OnErrorListener, + IjkMediaPlayer.OnPreparedListener { + private static final String TAG = "AudioPlayerActivity"; + private static final int REQ_DELAY_MILLS = 3000; + + private View mBufferingIndicator; + private MediaController mMediaController; + private AudioPlayer mAudioPlayer; + + private String mAudioPath; + private Button mBackBtn; + private long mLastPosition = 0; + private boolean mIsLiveStream = false; + + private int mReqDelayMills = REQ_DELAY_MILLS; + private boolean mIsCompleted = false; + private Runnable mVideoReconnect; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + setContentView(R.layout.activity_player); + + mAudioPath = getIntent().getStringExtra("audioPath"); + + Intent intent = getIntent(); + String intentAction = intent.getAction(); + if (!TextUtils.isEmpty(intentAction) && intentAction.equals(Intent.ACTION_VIEW)) { + mAudioPath = intent.getDataString(); + } + + mBackBtn = (Button) findViewById(R.id.back_btn); + mBackBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onBackPressed(); + finish(); + } + }); + mBufferingIndicator = findViewById(R.id.buffering_indicator); + + boolean useFastForward = true; + boolean disableProgressBar = false; + // Tip: you can custom the variable depending on your situation + mIsLiveStream = true; + if (mIsLiveStream) { + disableProgressBar = true; + useFastForward = false; + } + mMediaController = new MediaController(this, useFastForward, disableProgressBar); + mAudioPlayer = new AudioPlayer(this); + + AVOptions options = new AVOptions(); + options.setInteger(AVOptions.KEY_MEDIACODEC, 1); // 1 -> enable, 0 -> disable + + Log.i(TAG, "mIsLiveStream:" + mIsLiveStream); + if (mIsLiveStream) { + options.setInteger(AVOptions.KEY_BUFFER_TIME, 1000); // the unit of buffer time is ms + options.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 8 * 1000); // the unit of timeout is ms + options.setString(AVOptions.KEY_FFLAGS, AVOptions.VALUE_FFLAGS_NOBUFFER); // "nobuffer" + options.setInteger(AVOptions.KEY_LIVE_STREAMING, 1); + } + mAudioPlayer.setAVOptions(options); + + mMediaController.setMediaPlayer(mAudioPlayer); + mAudioPlayer.setMediaController(mMediaController); + mAudioPlayer.setOnErrorListener(this); + mAudioPlayer.setOnCompletionListener(this); + mAudioPlayer.setOnInfoListener(this); + mAudioPlayer.setOnPreparedListener(this); + mAudioPlayer.setAudioPath(mAudioPath); + + mAudioPlayer.start(); + mBufferingIndicator.setVisibility(View.VISIBLE); + } + + @Override + public void onCompletion(IMediaPlayer mp) { + Log.d(TAG, "onCompletion"); + mIsCompleted = true; + mBufferingIndicator.setVisibility(View.GONE); + } + + @Override + public boolean onError(IMediaPlayer mp, int what, int extra) { + Log.d(TAG, "onError what=" + what + ", extra=" + extra); + if (what == -10000) { + if (extra == PlayerCode.EXTRA_CODE_INVALID_URI || extra == PlayerCode.EXTRA_CODE_EOF) { + if (mBufferingIndicator != null) + mBufferingIndicator.setVisibility(View.GONE); + return true; + } + if (mIsCompleted && extra == PlayerCode.EXTRA_CODE_EMPTY_PLAYLIST) { + Log.d(TAG, "mVideoView reconnect!!!"); + mVideoReconnect = new Runnable() { + @Override + public void run() { + mAudioPlayer.setAudioPath(mAudioPath); + } + }; + mReqDelayMills += 200; + } else if (extra == PlayerCode.EXTRA_CODE_404_NOT_FOUND) { + // NO ts exist + if (mBufferingIndicator != null) + mBufferingIndicator.setVisibility(View.GONE); + } else if (extra == PlayerCode.EXTRA_CODE_IO_ERROR) { + // NO rtmp stream exist + if (mBufferingIndicator != null) + mBufferingIndicator.setVisibility(View.GONE); + } + } + // return true means you handle the onError, hence System wouldn't handle it again(popup a dialog). + return true; + } + + @Override + public boolean onInfo(IMediaPlayer mp, int what, int extra) { + Log.d(TAG, "onInfo what=" + what + ", extra=" + extra); + if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) { + Log.i(TAG, "onInfo: (MEDIA_INFO_BUFFERING_START)"); + if (mBufferingIndicator != null) + mBufferingIndicator.setVisibility(View.VISIBLE); + } else if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_END) { + Log.i(TAG, "onInfo: (MEDIA_INFO_BUFFERING_END)"); + if (mBufferingIndicator != null) + mBufferingIndicator.setVisibility(View.GONE); + } + return true; + } + + @Override + public void onPrepared(IMediaPlayer mp) { + Log.d(TAG, "onPrepared"); + mBufferingIndicator.setVisibility(View.GONE); + mReqDelayMills = REQ_DELAY_MILLS; + } + + @Override + public void onResume() { + super.onResume(); + mReqDelayMills = REQ_DELAY_MILLS; + Log.i(TAG, "onResume"); + if (mAudioPlayer != null && !mIsLiveStream && mLastPosition != 0) { + mAudioPlayer.seekTo(mLastPosition); + mAudioPlayer.start(); + } + } + + @Override + public void onPause() { +// if (mAudioPlayer != null) { +// mAudioPlayer.pause(); +// mLastPosition = mAudioPlayer.getCurrentPosition(); +// } + if (mAudioPlayer != null) { + mLastPosition = mAudioPlayer.getCurrentPosition(); + mAudioPlayer.stopPlayback(); + } + super.onPause(); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if(mAudioPlayer.onKeyDown(keyCode, event)) { + return true; + } + return super.onKeyDown(keyCode, event); + } + +} diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/MainActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/MainActivity.java index ac950be..4e041c7 100644 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/MainActivity.java +++ b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/MainActivity.java @@ -1,120 +1,168 @@ package com.pili.pldroid.playerdemo; -import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; +import android.database.Cursor; +import android.provider.MediaStore; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.SimpleCursorAdapter; +import android.support.v7.app.ActionBarActivity; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; -import android.widget.ArrayAdapter; +import android.widget.AdapterView; +import android.widget.Button; import android.widget.EditText; -import android.widget.Spinner; -import android.widget.TextView; +import android.widget.ListView; +import android.widget.Toast; -public class MainActivity extends AppCompatActivity { +import com.pili.pldroid.playerdemo.R; - private static final String DEFAULT_TEST_URL = "rtmp://live.hkstv.hk.lxdns.com/live/hks"; - private Spinner mActivitySpinner; - private EditText mEditText; - private int mIsHwCodecEnabled = 0; +public class MainActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks { - public static final String[] TEST_ACTIVITY_ARRAY = { - "PLMediaPlayerActivity", - "PLAudioPlayerActivity", - "PLVideoViewActivity", - "PLVideoTextureActivity", - "VideoViewActivity" - }; + private static final String MSG_NOT_ALLOW_EMPTY_URL = "Error! URL is empty!"; + + private ListView fileListView; + + private VideoAdapter adapter; + + private boolean changed = false; + private EditText mInputUrlEditText; + private Button mVideoBtn; + private Button mAudioBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - mEditText = (EditText)findViewById(R.id.VideoPathEdit); - mEditText.setText(DEFAULT_TEST_URL); + fileListView = (ListView) findViewById(R.id.fileListView); + adapter = new VideoAdapter(this); + fileListView.setAdapter(adapter); + fileListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, final int position, final long id) { + Intent intent = new Intent(MainActivity.this, VideoPlayerActivity.class); + intent.putExtra("videoPath", adapter.getVideoPath(position)); + startActivity(intent); + } + }); - mActivitySpinner = (Spinner) findViewById(R.id.TestSpinner); - ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, TEST_ACTIVITY_ARRAY); - mActivitySpinner.setAdapter(adapter); - } + mInputUrlEditText = (EditText) findViewById(R.id.input_url); + + mVideoBtn = (Button) findViewById(R.id.btn_video); + mVideoBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String url = mInputUrlEditText.getText().toString().trim(); + if (url == null || url.isEmpty()) { + Toast.makeText(getApplicationContext(), MSG_NOT_ALLOW_EMPTY_URL, Toast.LENGTH_SHORT).show(); + return; + } + Intent intent = new Intent(MainActivity.this, VideoPlayerActivity.class); + intent.putExtra("videoPath", url); + startActivity(intent); + } + }); - public void onClickPlaySetting(View v) { - showPlaySettingDialog(); + mAudioBtn = (Button) findViewById(R.id.btn_audio); + mAudioBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String url = mInputUrlEditText.getText().toString().trim(); + if (url == null || url.isEmpty()) { + Toast.makeText(getApplicationContext(), MSG_NOT_ALLOW_EMPTY_URL, Toast.LENGTH_SHORT).show(); + return; + } + Intent intent = new Intent(MainActivity.this, AudioPlayerActivity.class); + intent.putExtra("audioPath", url); + startActivity(intent); + } + }); + refreshUI(); + getSupportLoaderManager().initLoader(1, null, this); } - public void onClickLocalFile(View v) { - Intent intent = new Intent(this, VideoFileActivity.class); - startActivityForResult(intent, 0); + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; } - public void onClickPlay(View v) { - String videopath = mEditText.getText().toString(); - if (!"".equals(videopath)) { - jumpToPlayerActivity(videopath); + private void refreshUI() { + if (changed) { + fileListView.setVisibility(View.GONE); + mInputUrlEditText.setVisibility(View.VISIBLE); + mAudioBtn.setVisibility(View.VISIBLE); + mVideoBtn.setVisibility(View.VISIBLE); + } else { + fileListView.setVisibility(View.VISIBLE); + mInputUrlEditText.setVisibility(View.GONE); + mAudioBtn.setVisibility(View.GONE); + mVideoBtn.setVisibility(View.GONE); } } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); - public void jumpToPlayerActivity(String videopath) { - Class cls = null; - switch (mActivitySpinner.getSelectedItemPosition()) { - case 0: cls = PLMediaPlayerActivity.class; - break; - case 1: cls = PLAudioPlayerActivity.class; - break; - case 2: cls = PLVideoViewActivity.class; - break; - case 3: cls = PLVideoTextureActivity.class; - break; - case 4: cls = VideoViewActivity.class; - break; - default: - return; + if (id == R.id.action_change) { + changed = !changed; + refreshUI(); + return true; } - Intent intent = new Intent(this, cls); - intent.putExtra("videoPath", videopath); - intent.putExtra("mediaCodec", mIsHwCodecEnabled); - startActivity(intent); + + return super.onOptionsItemSelected(item); } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - String videoPath = data.getStringExtra("videoPath"); - mEditText.setText(videoPath, TextView.BufferType.EDITABLE); + public Loader onCreateLoader(int id, Bundle args) { + return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, + "UPPER(" + MediaStore.Video.Media.DATA + ")"); } - protected void showPlaySettingDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - final View root = inflater.inflate(R.layout.dialog_setting,null); - final Spinner codecSpinner = (Spinner) root.findViewById(R.id.CodecSpinner); - codecSpinner.setAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, new String[] { - getString(R.string.sw_decode), getString(R.string.hw_decode) - })); - codecSpinner.setSelection(mIsHwCodecEnabled); - builder.setTitle(getString(R.string.play_setting)); - builder.setView(root); - final AlertDialog dialog = builder.create(); - dialog.setCancelable(false); - dialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.dlg_ok), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mIsHwCodecEnabled = codecSpinner.getSelectedItemPosition(); + @Override + public void onLoadFinished(Loader loader, Cursor data) { + adapter.swapCursor(data); + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + class VideoAdapter extends SimpleCursorAdapter { + public VideoAdapter(Context context) { + super(context, android.R.layout.simple_list_item_2, null, + new String[]{MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATA}, + new int[]{android.R.id.text1, android.R.id.text2}, 0); + } + + @Override + public long getItemId(int position) { + final Cursor cursor = getCursor(); + if (cursor.getCount() == 0 || position >= cursor.getCount()) { + return 0; } - }); - dialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.dlg_cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { + cursor.moveToPosition(position); + return cursor.getLong(0); + } + + public String getVideoPath(int position) { + final Cursor cursor = getCursor(); + if (cursor.getCount() == 0) { + return ""; } - }); - dialog.show(); + cursor.moveToPosition(position); + + return cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); + } } } diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLAudioPlayerActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLAudioPlayerActivity.java deleted file mode 100644 index 672c0cf..0000000 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLAudioPlayerActivity.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.pili.pldroid.playerdemo; - -import android.content.Context; -import android.media.AudioManager; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.View; - -import com.pili.pldroid.player.PLMediaPlayer; - -import java.io.IOException; - -/** - * This demo shows how to use PLMediaPlayer API playing audio stream - */ -public class PLAudioPlayerActivity extends AppCompatActivity { - - private static final String TAG = PLAudioPlayerActivity.class.getSimpleName(); - - private PLMediaPlayer mMediaPlayer; - private String mAudioPath; - private View mLoadingView; - private boolean mIsStopped = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_audio_player); - mLoadingView = findViewById(R.id.LoadingView); - mAudioPath = getIntent().getStringExtra("videoPath"); - - AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - mMediaPlayer = new PLMediaPlayer(); - mMediaPlayer.setOnPreparedListener(mOnPreparedListener); - mMediaPlayer.setOnCompletionListener(mOnCompletionListener); - mMediaPlayer.setOnErrorListener(mOnErrorListener); - mMediaPlayer.setOnInfoListener(mOnInfoListener); - mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); - - prepare(); - } - - @Override - protected void onDestroy() { - release(); - AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - audioManager.abandonAudioFocus(null); - super.onDestroy(); - } - - @Override - protected void onResume() { - super.onResume(); - mMediaPlayer.start(); - } - - @Override - protected void onPause() { - mMediaPlayer.pause(); - super.onPause(); - } - - public void onClickPlay(View v) { - if (mIsStopped) { - prepare(); - } else { - mMediaPlayer.start(); - } - } - - public void onClickPause(View v) { - mMediaPlayer.pause(); - } - - public void onClickResume(View v) { - mMediaPlayer.start(); - } - - public void onClickStop(View v) { - mMediaPlayer.stop(); - mMediaPlayer.reset(); - mIsStopped = true; - } - - public void release() { - mMediaPlayer.stop(); - mMediaPlayer.release(); - mMediaPlayer = null; - } - - private void prepare() { - try { - mMediaPlayer.setDataSource(mAudioPath); - mMediaPlayer.prepareAsync(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private PLMediaPlayer.OnPreparedListener mOnPreparedListener = new PLMediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(PLMediaPlayer mp) { - Log.i(TAG, "On Prepared !"); - mMediaPlayer.start(); - mIsStopped = false; - } - }; - - private PLMediaPlayer.OnInfoListener mOnInfoListener = new PLMediaPlayer.OnInfoListener() { - @Override - public boolean onInfo(PLMediaPlayer mp, int what, int extra) { - Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra); - switch (what) { - case PLMediaPlayer.MEDIA_INFO_BUFFERING_START: - mLoadingView.setVisibility(View.VISIBLE); - break; - case PLMediaPlayer.MEDIA_INFO_BUFFERING_END: - case PLMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START: - mLoadingView.setVisibility(View.GONE); - break; - default: - break; - } - return true; - } - }; - - private PLMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new PLMediaPlayer.OnBufferingUpdateListener() { - @Override - public void onBufferingUpdate(PLMediaPlayer mp, int percent) { - Log.d(TAG, "onBufferingUpdate: " + percent + "%"); - } - }; - - /** - * Listen the event of playing complete - * For playing local file, it's called when reading the file EOF - * For playing network stream, it's called when the buffered bytes played over - * - * If setLooping(true) is called, the player will restart automatically - * And `onCompletion` will not be called - */ - private PLMediaPlayer.OnCompletionListener mOnCompletionListener = new PLMediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(PLMediaPlayer mp) { - Log.d(TAG, "Play Completed !"); - } - }; - - private PLMediaPlayer.OnErrorListener mOnErrorListener = new PLMediaPlayer.OnErrorListener() { - @Override - public boolean onError(PLMediaPlayer mp, int errorCode) { - Log.e(TAG, "Error happened, errorCode = " + errorCode); - switch (errorCode) { - case PLMediaPlayer.ERROR_CODE_INVALID_URI: - break; - case PLMediaPlayer.ERROR_CODE_404_NOT_FOUND: - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: - break; - case PLMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: - break; - case PLMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: - break; - case PLMediaPlayer.MEDIA_ERROR_UNKNOWN: - break; - default: - break; - } - // Todo pls handle the error status here, retry or call finish() - finish(); - // The PLMediaPlayer has moved to the Error state, if you want to retry, must reset first ! - // try { - // mMediaPlayer.reset(); - // mMediaPlayer.setDataSource(mAudioPath); - // mMediaPlayer.prepareAsync(); - // } catch (IOException e) { - // e.printStackTrace(); - // } - // Return true means the error has been handled - // If return false, then `onCompletion` will be called - return true; - } - }; -} diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLMediaPlayerActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLMediaPlayerActivity.java deleted file mode 100644 index 6506266..0000000 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLMediaPlayerActivity.java +++ /dev/null @@ -1,290 +0,0 @@ -package com.pili.pldroid.playerdemo; - -import android.content.Context; -import android.media.AudioManager; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.Gravity; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.View; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.Toast; - -import com.pili.pldroid.player.AVOptions; -import com.pili.pldroid.player.PLMediaPlayer; - -import java.io.IOException; - -/** - * This demo shows how to use PLMediaPlayer API playing video stream - */ -public class PLMediaPlayerActivity extends AppCompatActivity { - - private static final String TAG = PLMediaPlayerActivity.class.getSimpleName(); - - private SurfaceView mSurfaceView; - private PLMediaPlayer mMediaPlayer; - private View mLoadingView; - private AVOptions mAVOptions; - - private int mSurfaceWidth = 0; - private int mSurfaceHeight = 0; - - private String mVideoPath = null; - - @Override - protected void onCreate(Bundle savedInstanceState) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_media_player); - mLoadingView = findViewById(R.id.LoadingView); - mSurfaceView = (SurfaceView) findViewById(R.id.SurfaceView); - mSurfaceView.getHolder().addCallback(mCallback); - - mVideoPath = getIntent().getStringExtra("videoPath"); - - mAVOptions = new AVOptions(); - - if (isLiveStreaming(mVideoPath)) { - // the unit of timeout is ms - mAVOptions.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); - // Some optimization with buffering mechanism when be set to 1 - mAVOptions.setInteger(AVOptions.KEY_LIVE_STREAMING, 1); - } - - // 1 -> hw codec enable, 0 -> disable [recommended] - int codec = getIntent().getIntExtra("mediaCodec", 0); - mAVOptions.setInteger(AVOptions.KEY_MEDIACODEC, codec); - - AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - } - - @Override - protected void onDestroy() { - release(); - AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - audioManager.abandonAudioFocus(null); - super.onDestroy(); - } - - public void onClickPlay(View v) { - prepare(); - } - - public void onClickPause(View v) { - if (mMediaPlayer != null) { - mMediaPlayer.pause(); - } - } - - public void onClickResume(View v) { - if (mMediaPlayer != null) { - mMediaPlayer.start(); - } - } - - public void onClickStop(View v) { - if (mMediaPlayer != null) { - mMediaPlayer.stop(); - mMediaPlayer.reset(); - } - } - - public void release() { - if (mMediaPlayer != null) { - mMediaPlayer.stop(); - mMediaPlayer.release(); - mMediaPlayer = null; - } - } - - private void prepare() { - - if (mMediaPlayer != null) { - release(); - } - - try { - mMediaPlayer = new PLMediaPlayer(mAVOptions); - - mMediaPlayer.setOnPreparedListener(mOnPreparedListener); - mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); - mMediaPlayer.setOnCompletionListener(mOnCompletionListener); - mMediaPlayer.setOnErrorListener(mOnErrorListener); - mMediaPlayer.setOnInfoListener(mOnInfoListener); - mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener); - - // set replay if completed - // mMediaPlayer.setLooping(true); - - mMediaPlayer.setDataSource(mVideoPath); - mMediaPlayer.setDisplay(mSurfaceView.getHolder()); - mMediaPlayer.prepareAsync(); - - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private SurfaceHolder.Callback mCallback = new SurfaceHolder.Callback() { - - @Override - public void surfaceCreated(SurfaceHolder holder) { - prepare(); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - mSurfaceWidth = width; - mSurfaceHeight = height; - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - release(); - } - }; - - private PLMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new PLMediaPlayer.OnVideoSizeChangedListener() { - public void onVideoSizeChanged(PLMediaPlayer mp, int width, int height) { - Log.i(TAG, "onVideoSizeChanged, width = "+ width + ",height = " + height); - // resize the display window to fit the screen - if (width != 0 && height != 0) { - float ratioW = (float) width/(float) mSurfaceWidth; - float ratioH = (float) height/(float) mSurfaceHeight; - float ratio = Math.max(ratioW, ratioH); - width = (int) Math.ceil((float)width/ratio); - height = (int) Math.ceil((float)height/ratio); - FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(width, height); - layout.gravity = Gravity.CENTER; - mSurfaceView.setLayoutParams(layout); - } - } - }; - - private PLMediaPlayer.OnPreparedListener mOnPreparedListener = new PLMediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(PLMediaPlayer mp) { - Log.i(TAG, "On Prepared !"); - mMediaPlayer.start(); - } - }; - - private PLMediaPlayer.OnInfoListener mOnInfoListener = new PLMediaPlayer.OnInfoListener() { - @Override - public boolean onInfo(PLMediaPlayer mp, int what, int extra) { - Log.i(TAG, "OnInfo, what = " + what + ", extra = " + extra); - switch (what) { - case PLMediaPlayer.MEDIA_INFO_BUFFERING_START: - mLoadingView.setVisibility(View.VISIBLE); - break; - case PLMediaPlayer.MEDIA_INFO_BUFFERING_END: - case PLMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: - mLoadingView.setVisibility(View.GONE); - break; - default: - break; - } - return true; - } - }; - - private PLMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new PLMediaPlayer.OnBufferingUpdateListener() { - @Override - public void onBufferingUpdate(PLMediaPlayer mp, int percent) { - Log.d(TAG, "onBufferingUpdate: " + percent + "%"); - } - }; - - /** - * Listen the event of playing complete - * For playing local file, it's called when reading the file EOF - * For playing network stream, it's called when the buffered bytes played over - * - * If setLooping(true) is called, the player will restart automatically - * And `onCompletion` will not be called - * - */ - private PLMediaPlayer.OnCompletionListener mOnCompletionListener = new PLMediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(PLMediaPlayer mp) { - Log.d(TAG, "Play Completed !"); - showToastTips("Play Completed !"); - finish(); - } - }; - - private PLMediaPlayer.OnErrorListener mOnErrorListener = new PLMediaPlayer.OnErrorListener() { - @Override - public boolean onError(PLMediaPlayer mp, int errorCode) { - Log.e(TAG, "Error happened, errorCode = " + errorCode); - switch (errorCode) { - case PLMediaPlayer.ERROR_CODE_INVALID_URI: - showToastTips("Invalid URL !"); - break; - case PLMediaPlayer.ERROR_CODE_404_NOT_FOUND: - showToastTips("404 resource not found !"); - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: - showToastTips("Connection refused !"); - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: - showToastTips("Connection timeout !"); - break; - case PLMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: - showToastTips("Empty playlist !"); - break; - case PLMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: - showToastTips("Stream disconnected !"); - break; - case PLMediaPlayer.ERROR_CODE_IO_ERROR: - showToastTips("Network IO Error !"); - break; - case PLMediaPlayer.MEDIA_ERROR_UNKNOWN: - default: - showToastTips("unknown error !"); - break; - } - // Todo pls handle the error status here, retry or call finish() - finish(); - // The PLMediaPlayer has moved to the Error state, if you want to retry, must reset first ! - // try { - // mMediaPlayer.reset(); - // mMediaPlayer.setDisplay(mSurfaceView.getHolder()); - // mMediaPlayer.setDataSource(mVideoPath); - // mMediaPlayer.prepareAsync(); - // } catch (IOException e) { - // e.printStackTrace(); - // } - // Return true means the error has been handled - // If return false, then `onCompletion` will be called - return true; - } - }; - - private void showToastTips(final String tips) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(PLMediaPlayerActivity.this, tips, Toast.LENGTH_LONG).show(); - } - }); - } - - private boolean isLiveStreaming(String url) { - if (url.startsWith("rtmp://") - || (url.startsWith("http://") && url.endsWith(".m3u8")) - || (url.startsWith("http://") && url.endsWith(".flv"))) { - return true; - } - return false; - } -} diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLVideoTextureActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLVideoTextureActivity.java deleted file mode 100644 index b68c22c..0000000 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLVideoTextureActivity.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.pili.pldroid.playerdemo; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.view.WindowManager; -import android.widget.Toast; - -import com.pili.pldroid.player.AVOptions; -import com.pili.pldroid.player.PLMediaPlayer; -import com.pili.pldroid.player.widget.PLVideoTextureView; -import com.pili.pldroid.playerdemo.widget.MediaController; - -/** - * This is a demo activity of PLVideoTextureView - */ -public class PLVideoTextureActivity extends AppCompatActivity { - - private MediaController mMediaController; - private PLVideoTextureView mVideoView; - private Toast mToast = null; - private String mVideoPath = null; - private int mRotation = 0; - private int mDisplayAspectRatio = PLVideoTextureView.ASPECT_RATIO_FIT_PARENT; //default - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - setContentView(R.layout.activity_pl_video_texture); - mVideoView = (PLVideoTextureView) findViewById(R.id.VideoView); - - View loadingView = findViewById(R.id.LoadingView); - mVideoView.setBufferingIndicator(loadingView); - - mVideoPath = getIntent().getStringExtra("videoPath"); - - // If you want to fix display orientation such as landscape, you can use the code show as follow - // - // if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { - // mVideoView.setPreviewOrientation(0); - // } - // else if (this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { - // mVideoView.setPreviewOrientation(270); - // } - - mVideoPath = getIntent().getStringExtra("videoPath"); - - AVOptions options = new AVOptions(); - - if (isLiveStreaming(mVideoPath)) { - // the unit of timeout is ms - options.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); - // Some optimization with buffering mechanism when be set to 1 - options.setInteger(AVOptions.KEY_LIVE_STREAMING, 1); - } - - // 1 -> hw codec enable, 0 -> disable [recommended] - int codec = getIntent().getIntExtra("mediaCodec", 0); - options.setInteger(AVOptions.KEY_MEDIACODEC, codec); - - mVideoView.setAVOptions(options); - - // You can also use a custom `MediaController` widget - mMediaController = new MediaController(this, false, isLiveStreaming(mVideoPath)); - mVideoView.setMediaController(mMediaController); - - mVideoView.setOnCompletionListener(mOnCompletionListener); - mVideoView.setOnErrorListener(mOnErrorListener); - - // After setVideoPath, the play will start automatically - mVideoView.setVideoPath(mVideoPath); - } - - @Override - protected void onPause() { - mVideoView.pause(); - super.onPause(); - } - - @Override - protected void onResume() { - super.onResume(); - mVideoView.start(); - } - - @Override - protected void onDestroy() { - mVideoView.stopPlayback(); - super.onDestroy(); - } - - public void onClickRotate(View v) { - mRotation = (mRotation + 90) % 360; - mVideoView.setDisplayOrientation(mRotation); - } - - public void onClickSwitchScreen(View v) { - mDisplayAspectRatio = (mDisplayAspectRatio + 1) % 5; - mVideoView.setDisplayAspectRatio(mDisplayAspectRatio); - switch (mVideoView.getDisplayAspectRatio()) { - case PLVideoTextureView.ASPECT_RATIO_ORIGIN: - showToastTips("Origin mode"); - break; - case PLVideoTextureView.ASPECT_RATIO_FIT_PARENT: - showToastTips("Fit parent !"); - break; - case PLVideoTextureView.ASPECT_RATIO_PAVED_PARENT: - showToastTips("Paved parent !"); - break; - case PLVideoTextureView.ASPECT_RATIO_16_9: - showToastTips("16 : 9 !"); - break; - case PLVideoTextureView.ASPECT_RATIO_4_3: - showToastTips("4 : 3 !"); - break; - default: - break; - } - } - - private PLMediaPlayer.OnErrorListener mOnErrorListener = new PLMediaPlayer.OnErrorListener() { - @Override - public boolean onError(PLMediaPlayer mp, int errorCode) { - switch (errorCode) { - case PLMediaPlayer.ERROR_CODE_INVALID_URI: - showToastTips("Invalid URL !"); - break; - case PLMediaPlayer.ERROR_CODE_404_NOT_FOUND: - showToastTips("404 resource not found !"); - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: - showToastTips("Connection refused !"); - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: - showToastTips("Connection timeout !"); - break; - case PLMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: - showToastTips("Empty playlist !"); - break; - case PLMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: - showToastTips("Stream disconnected !"); - break; - case PLMediaPlayer.ERROR_CODE_IO_ERROR: - showToastTips("Network IO Error !"); - break; - case PLMediaPlayer.MEDIA_ERROR_UNKNOWN: - default: - showToastTips("unknown error !"); - break; - } - // Todo pls handle the error status here, retry or call finish() - finish(); - // If you want to retry, do like this: - // mVideoView.setVideoPath(mVideoPath); - // Return true means the error has been handled - // If return false, then `onCompletion` will be called - return true; - } - }; - - private PLMediaPlayer.OnCompletionListener mOnCompletionListener = new PLMediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(PLMediaPlayer plMediaPlayer) { - showToastTips("Play Completed !"); - finish(); - } - }; - - private void showToastTips(final String tips) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (mToast != null) { - mToast.cancel(); - } - mToast = Toast.makeText(PLVideoTextureActivity.this, tips, Toast.LENGTH_SHORT); - mToast.show(); - } - }); - } - - private boolean isLiveStreaming(String url) { - if (url.startsWith("rtmp://") - || (url.startsWith("http://") && url.endsWith(".m3u8")) - || (url.startsWith("http://") && url.endsWith(".flv"))) { - return true; - } - return false; - } -} diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLVideoViewActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLVideoViewActivity.java deleted file mode 100644 index ed0f266..0000000 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/PLVideoViewActivity.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.pili.pldroid.playerdemo; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.Toast; - -import com.pili.pldroid.player.AVOptions; -import com.pili.pldroid.player.PLMediaPlayer; -import com.pili.pldroid.player.widget.PLVideoView; -import com.pili.pldroid.playerdemo.widget.MediaController; - -/** - * This is a demo activity of PLVideoView - */ -public class PLVideoViewActivity extends AppCompatActivity { - - private static final String TAG = PLVideoViewActivity.class.getSimpleName(); - - private MediaController mMediaController; - private PLVideoView mVideoView; - private Toast mToast = null; - private String mVideoPath = null; - private int mDisplayAspectRatio = PLVideoView.ASPECT_RATIO_FIT_PARENT; - - @Override - protected void onCreate(Bundle savedInstanceState) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_pl_video_view); - mVideoView = (PLVideoView) findViewById(R.id.VideoView); - - View loadingView = findViewById(R.id.LoadingView); - mVideoView.setBufferingIndicator(loadingView); - - mVideoPath = getIntent().getStringExtra("videoPath"); - - AVOptions options = new AVOptions(); - - if (isLiveStreaming(mVideoPath)) { - // the unit of timeout is ms - options.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); - // Some optimization with buffering mechanism when be set to 1 - options.setInteger(AVOptions.KEY_LIVE_STREAMING, 1); - } - - // 1 -> hw codec enable, 0 -> disable [recommended] - int codec = getIntent().getIntExtra("mediaCodec", 0); - options.setInteger(AVOptions.KEY_MEDIACODEC, codec); - - mVideoView.setAVOptions(options); - - // Set some listeners - mVideoView.setOnPreparedListener(mOnPreparedListener); - mVideoView.setOnInfoListener(mOnInfoListener); - mVideoView.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener); - mVideoView.setOnBufferingUpdateListener(mOnBufferingUpdateListener); - mVideoView.setOnCompletionListener(mOnCompletionListener); - mVideoView.setOnSeekCompleteListener(mOnSeekCompleteListener); - mVideoView.setOnErrorListener(mOnErrorListener); - - // After setVideoPath, the play will start automatically - // mVideoView.start() is not required - mVideoView.setVideoPath(mVideoPath); - - // You can also use a custom `MediaController` widget - mMediaController = new MediaController(this, false, isLiveStreaming(mVideoPath)); - mVideoView.setMediaController(mMediaController); - } - - @Override - protected void onResume() { - super.onResume(); - mVideoView.start(); - } - - @Override - protected void onPause() { - mVideoView.pause(); - super.onPause(); - } - - @Override - protected void onDestroy() { - mVideoView.stopPlayback(); - super.onDestroy(); - } - - public void onClickSwitchScreen(View v) { - mDisplayAspectRatio = (mDisplayAspectRatio + 1) % 5; - mVideoView.setDisplayAspectRatio(mDisplayAspectRatio); - switch (mVideoView.getDisplayAspectRatio()) { - case PLVideoView.ASPECT_RATIO_ORIGIN: - showToastTips("Origin mode"); - break; - case PLVideoView.ASPECT_RATIO_FIT_PARENT: - showToastTips("Fit parent !"); - break; - case PLVideoView.ASPECT_RATIO_PAVED_PARENT: - showToastTips("Paved parent !"); - break; - case PLVideoView.ASPECT_RATIO_16_9: - showToastTips("16 : 9 !"); - break; - case PLVideoView.ASPECT_RATIO_4_3: - showToastTips("4 : 3 !"); - break; - default: - break; - } - } - - private PLMediaPlayer.OnPreparedListener mOnPreparedListener = new PLMediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(PLMediaPlayer plMediaPlayer) { - Log.d(TAG, "onPrepared ! "); - } - }; - - private PLMediaPlayer.OnInfoListener mOnInfoListener = new PLMediaPlayer.OnInfoListener() { - @Override - public boolean onInfo(PLMediaPlayer plMediaPlayer, int what, int extra) { - Log.d(TAG, "onInfo: " + what + ", " + extra); - return false; - } - }; - - private PLMediaPlayer.OnErrorListener mOnErrorListener = new PLMediaPlayer.OnErrorListener() { - @Override - public boolean onError(PLMediaPlayer plMediaPlayer, int errorCode) { - Log.e(TAG, "Error happened, errorCode = " + errorCode); - switch (errorCode) { - case PLMediaPlayer.ERROR_CODE_INVALID_URI: - showToastTips("Invalid URL !"); - break; - case PLMediaPlayer.ERROR_CODE_404_NOT_FOUND: - showToastTips("404 resource not found !"); - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_REFUSED: - showToastTips("Connection refused !"); - break; - case PLMediaPlayer.ERROR_CODE_CONNECTION_TIMEOUT: - showToastTips("Connection timeout !"); - break; - case PLMediaPlayer.ERROR_CODE_EMPTY_PLAYLIST: - showToastTips("Empty playlist !"); - break; - case PLMediaPlayer.ERROR_CODE_STREAM_DISCONNECTED: - showToastTips("Stream disconnected !"); - break; - case PLMediaPlayer.ERROR_CODE_IO_ERROR: - showToastTips("Network IO Error !"); - break; - case PLMediaPlayer.MEDIA_ERROR_UNKNOWN: - default: - showToastTips("unknown error !"); - break; - } - // Todo pls handle the error status here, retry or call finish() - finish(); - // If you want to retry, do like this: - // mVideoView.setVideoPath(mVideoPath); - // Return true means the error has been handled - // If return false, then `onCompletion` will be called - return true; - } - }; - - private PLMediaPlayer.OnCompletionListener mOnCompletionListener = new PLMediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(PLMediaPlayer plMediaPlayer) { - Log.d(TAG, "Play Completed !"); - showToastTips("Play Completed !"); - finish(); - } - }; - - private PLMediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener = new PLMediaPlayer.OnBufferingUpdateListener() { - @Override - public void onBufferingUpdate(PLMediaPlayer plMediaPlayer, int precent) { - Log.d(TAG, "onBufferingUpdate: " + precent); - } - }; - - private PLMediaPlayer.OnSeekCompleteListener mOnSeekCompleteListener = new PLMediaPlayer.OnSeekCompleteListener() { - @Override - public void onSeekComplete(PLMediaPlayer plMediaPlayer) { - Log.d(TAG, "onSeekComplete !"); - }; - }; - - private PLMediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener = new PLMediaPlayer.OnVideoSizeChangedListener() { - @Override - public void onVideoSizeChanged(PLMediaPlayer plMediaPlayer, int width, int height) { - Log.d(TAG, "onVideoSizeChanged: " + width + "," + height); - } - }; - - private void showToastTips(final String tips) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (mToast != null) { - mToast.cancel(); - } - mToast = Toast.makeText(PLVideoViewActivity.this, tips, Toast.LENGTH_SHORT); - mToast.show(); - } - }); - } - - private boolean isLiveStreaming(String url) { - if (url.startsWith("rtmp://") - || (url.startsWith("http://") && url.endsWith(".m3u8")) - || (url.startsWith("http://") && url.endsWith(".flv"))) { - return true; - } - return false; - } -} diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoFileActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoFileActivity.java deleted file mode 100644 index 5415938..0000000 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoFileActivity.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.pili.pldroid.playerdemo; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.provider.MediaStore; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v4.widget.SimpleCursorAdapter; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ListView; - -public class VideoFileActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks { - - private ListView mVideoListView; - private VideoAdapter mVideoAdapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_video_file); - - mVideoListView = (ListView) findViewById(R.id.FileListView); - mVideoAdapter = new VideoAdapter(this); - - mVideoListView.setAdapter(mVideoAdapter); - mVideoListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, final int position, final long id) { - Intent intent = new Intent(); - intent.putExtra("videoPath", mVideoAdapter.getVideoPath(position)); - setResult(Activity.RESULT_OK, intent); - finish(); - } - }); - - getSupportLoaderManager().initLoader(1, null, this); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return new CursorLoader(this, MediaStore.Video.Media.getContentUri("external"), null, null, null, - "UPPER(" + MediaStore.Video.Media.DATA + ")"); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - mVideoAdapter.swapCursor(data); - } - - @Override - public void onLoaderReset(Loader loader) { - - } - - public class VideoAdapter extends SimpleCursorAdapter { - - public VideoAdapter(Context context) { - super(context, android.R.layout.simple_list_item_1, null, - new String[]{MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DATA}, - new int[]{android.R.id.text1, android.R.id.text2}, 0); - } - - @Override - public long getItemId(int position) { - final Cursor cursor = getCursor(); - if (cursor == null || cursor.getCount() == 0 || position >= cursor.getCount()) { - return 0; - } - cursor.moveToPosition(position); - return cursor.getLong(0); - } - - public String getVideoPath(int position) { - final Cursor cursor = getCursor(); - if (cursor.getCount() == 0) { - return ""; - } - cursor.moveToPosition(position); - return cursor.getString(cursor.getColumnIndex(MediaStore.Video.Media.DATA)); - } - } - -} diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoViewActivity.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoPlayerActivity.java similarity index 87% rename from PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoViewActivity.java rename to PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoPlayerActivity.java index 13bcbfd..1ad03a9 100644 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoViewActivity.java +++ b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/VideoPlayerActivity.java @@ -1,9 +1,10 @@ package com.pili.pldroid.playerdemo; +import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -15,8 +16,8 @@ import com.pili.pldroid.player.AVOptions; import com.pili.pldroid.player.PlayerCode; +import com.pili.pldroid.player.SharedLibraryNameHelper; import com.pili.pldroid.player.common.Util; -import com.pili.pldroid.player.widget.PLVideoView; import com.pili.pldroid.player.widget.VideoView; import com.pili.pldroid.playerdemo.widget.AspectLayout; import com.pili.pldroid.playerdemo.widget.MediaController; @@ -24,19 +25,13 @@ import tv.danmaku.ijk.media.player.IMediaPlayer; import tv.danmaku.ijk.media.player.IjkMediaPlayer; -/** - * This is a demo activity of com.pili.pldroid.player.widget.VideoView - * @deprecated Use {@link PLVideoView} instead. - */ -public class VideoViewActivity extends AppCompatActivity implements +public class VideoPlayerActivity extends Activity implements IjkMediaPlayer.OnCompletionListener, IjkMediaPlayer.OnInfoListener, IjkMediaPlayer.OnErrorListener, IjkMediaPlayer.OnVideoSizeChangedListener, - IjkMediaPlayer.OnPreparedListener { - - private static final String TAG = "VideoViewActivity"; - + IjkMediaPlayer.OnPreparedListener{ + private static final String TAG = "VideoPlayerActivity"; private static final int REQ_DELAY_MILLS = 3000; private VideoView mVideoView; @@ -59,9 +54,7 @@ public class VideoViewActivity extends AppCompatActivity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - setContentView(R.layout.activity_video_view); - - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + setContentView(R.layout.activity_player); mVideoPath = getIntent().getStringExtra("videoPath"); @@ -71,9 +64,7 @@ protected void onCreate(Bundle savedInstanceState) { mVideoPath = intent.getDataString(); } - mVideoView = (VideoView) findViewById(R.id.video_view); mAspectLayout = (AspectLayout)findViewById(R.id.aspect_layout); - mBufferingIndicator = findViewById(R.id.buffering_indicator); mBackBtn = (Button) findViewById(R.id.back_btn); mBackBtn.setOnClickListener(new View.OnClickListener() { @@ -84,33 +75,40 @@ public void onClick(View view) { finish(); } }); - + mBufferingIndicator = findViewById(R.id.buffering_indicator); boolean useFastForward = true; boolean disableProgressBar = false; Log.i(TAG, "mVideoPath:" + mVideoPath); - - //SharedLibraryNameHelper.getInstance().renameSharedLibrary("pldroidplayer_v7a"); - - AVOptions options = new AVOptions(); - int codec = getIntent().getIntExtra("mediaCodec", 0); - options.setInteger(AVOptions.KEY_MEDIACODEC, codec); // 1 -> hw codec enable, 0 -> disable - - mIsLiveStream = isLiveStreaming(mVideoPath); - if (mIsLiveStream) { - options.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); // the unit of timeout is ms - options.setInteger(AVOptions.KEY_LIVE_STREAMING, 1); - } // Tip: you can custom the variable depending on your situation + mIsLiveStream = true; if (mIsLiveStream) { disableProgressBar = true; useFastForward = false; } mMediaController = new MediaController(this, useFastForward, disableProgressBar); + +// SharedLibraryNameHelper.getInstance().renameSharedLibrary("pldroidplayer_v7a"); + +// mVideoView = (VideoTextureView) findViewById(R.id.video_view); + mVideoView = (VideoView) findViewById(R.id.video_view); +// mVideoView.setVideoPath(mVideoPath); +// mVideoView.start(); mMediaController.setMediaPlayer(mVideoView); mVideoView.setMediaController(mMediaController); mVideoView.setMediaBufferingIndicator(mBufferingIndicator); + AVOptions options = new AVOptions(); + options.setInteger(AVOptions.KEY_MEDIACODEC, 0); // 1 -> enable, 0 -> disable + + Log.i(TAG, "mIsLiveStream:" + mIsLiveStream); + if (mIsLiveStream) { + options.setInteger(AVOptions.KEY_BUFFER_TIME, 1000); // the unit of buffer time is ms + options.setInteger(AVOptions.KEY_GET_AV_FRAME_TIMEOUT, 10 * 1000); // the unit of timeout is ms + options.setString(AVOptions.KEY_FFLAGS, AVOptions.VALUE_FFLAGS_NOBUFFER); // "nobuffer" + options.setInteger(AVOptions.KEY_LIVE_STREAMING, 1); + } + mVideoView.setAVOptions(options); mVideoView.setVideoPath(mVideoPath); @@ -122,7 +120,7 @@ public void onClick(View view) { mVideoView.setOnVideoSizeChangedListener(this); mVideoView.requestFocus(); - +// mVideoView.start(); mBufferingIndicator.setVisibility(View.VISIBLE); } @@ -170,9 +168,9 @@ public void run() { @Override public boolean onInfo(IMediaPlayer mp, int what, int extra) { - Log.d(TAG, "onInfo what=" + what + ", extra=" + extra); + if (what == IMediaPlayer.MEDIA_INFO_BUFFERING_START) { Log.i(TAG, "onInfo: (MEDIA_INFO_BUFFERING_START)"); if (mBufferingIndicator != null) @@ -188,7 +186,6 @@ public boolean onInfo(IMediaPlayer mp, int what, int extra) { Toast.makeText(this, "Video Start", Toast.LENGTH_LONG).show(); Log.i(TAG, "duration:" + mVideoView.getDuration()); } - return true; } @@ -215,25 +212,22 @@ public void onPause() { mLastPosition = mVideoView.getCurrentPosition(); mVideoView.pause(); } - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); super.onPause(); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } @Override public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int width, int height, int sarNum, int sarDen) { - Log.i(TAG, "onVideoSizeChanged " + iMediaPlayer.getVideoWidth() + "x" + iMediaPlayer.getVideoHeight() + ",width:" + width + ",height:" + height + ",sarDen:" + sarDen + ",sarNum:" + sarNum); - - /* if (width > height) { // land video setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + mScreenSize = Util.getResolution(this); } else { // port video setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); - }*/ - - mScreenSize = Util.getResolution(this); + mScreenSize = Util.getResolution(this); + } if (width < mScreenSize.first) { height = mScreenSize.first * height / width; @@ -250,13 +244,4 @@ public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int width, int height, mLayoutParams.height = height; mAspectLayout.setLayoutParams(mLayoutParams); } - - private boolean isLiveStreaming(String url) { - if (url.startsWith("rtmp://") - || (url.startsWith("http://") && url.endsWith(".m3u8")) - || (url.startsWith("http://") && url.endsWith(".flv"))) { - return true; - } - return false; - } } diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/common/Util.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/common/Util.java new file mode 100644 index 0000000..3358e02 --- /dev/null +++ b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/common/Util.java @@ -0,0 +1,16 @@ +package com.pili.pldroid.playerdemo.common; + +import android.net.Uri; + +/** + * Created by jerikc on 15/5/30. + */ +public class Util { + public static boolean isUrlLocalFile(String path) { + return getPathScheme(path) == null || "file".equals(getPathScheme(path)); + } + + public static String getPathScheme(String path) { + return Uri.parse(path).getScheme(); + } +} diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/AspectLayout.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/AspectLayout.java index c4d65d4..5c04048 100644 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/AspectLayout.java +++ b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/AspectLayout.java @@ -15,7 +15,6 @@ * Created by jerikc on 15/11/22. */ public class AspectLayout extends RelativeLayout { - private static final String TAG = "AspectLayout"; private int mWidthMeasureSpec; diff --git a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/MediaController.java b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/MediaController.java index 0a943fb..1fe0a66 100644 --- a/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/MediaController.java +++ b/PLDroidPlayerDemo/app/src/main/java/com/pili/pldroid/playerdemo/widget/MediaController.java @@ -27,13 +27,11 @@ import java.util.Locale; /** - * You can write a custom MediaController instead of this class - * A MediaController widget must implement all the interface defined by com.pili.pldroid.player.IMediaController + * Created by jerikc on 15/5/29. */ public class MediaController extends FrameLayout implements IMediaController { - - private static final String TAG = "PLMediaController"; - private IMediaController.MediaPlayerControl mPlayer; + private static final String TAG = "MyMediaController"; + private MediaPlayerControl mPlayer; private Context mContext; private PopupWindow mWindow; private int mAnimStyle; @@ -75,6 +73,7 @@ public class MediaController extends FrameLayout implements IMediaController { private Runnable mLastSeekBarRunnable; private boolean mDisableProgress = false; + public MediaController(Context context, AttributeSet attrs) { super(context, attrs); mRoot = this; @@ -110,7 +109,6 @@ private boolean initController(Context context) { public void onFinishInflate() { if (mRoot != null) initControllerView(mRoot); - super.onFinishInflate(); } private void initFloatingWindow() { @@ -377,7 +375,7 @@ public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { if (!fromuser) return; - final int newposition = (int) (mDuration * progress) / 1000; + final long newposition = (mDuration * progress) / 1000; String time = generateTime(newposition); if (mInstantSeeking) { mHandler.removeCallbacks(mLastSeekBarRunnable); @@ -395,7 +393,7 @@ public void run() { public void onStopTrackingTouch(SeekBar bar) { if (!mInstantSeeking) - mPlayer.seekTo((int)(mDuration * bar.getProgress()) / 1000); + mPlayer.seekTo((mDuration * bar.getProgress()) / 1000); show(sDefaultTimeout); mHandler.removeMessages(SHOW_PROGRESS); @@ -405,9 +403,9 @@ public void onStopTrackingTouch(SeekBar bar) { } }; - private OnClickListener mRewListener = new OnClickListener() { + private View.OnClickListener mRewListener = new View.OnClickListener() { public void onClick(View v) { - int pos = (int)mPlayer.getCurrentPosition(); + long pos = mPlayer.getCurrentPosition(); pos -= 5000; // milliseconds mPlayer.seekTo(pos); setProgress(); @@ -416,9 +414,9 @@ public void onClick(View v) { } }; - private OnClickListener mFfwdListener = new OnClickListener() { + private View.OnClickListener mFfwdListener = new View.OnClickListener() { public void onClick(View v) { - int pos = (int)mPlayer.getCurrentPosition(); + long pos = mPlayer.getCurrentPosition(); pos += 15000; // milliseconds mPlayer.seekTo(pos); setProgress(); @@ -530,7 +528,7 @@ public void hide() { if (mShowing) { if (mAnchor != null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - //mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + mAnchor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); } } try { diff --git a/PLDroidPlayerDemo/app/src/main/jniLibs/arm64-v8a/libpldroidplayer.so b/PLDroidPlayerDemo/app/src/main/jniLibs/arm64-v8a/libpldroidplayer.so old mode 100644 new mode 100755 index d16e20e..6905807 Binary files a/PLDroidPlayerDemo/app/src/main/jniLibs/arm64-v8a/libpldroidplayer.so and b/PLDroidPlayerDemo/app/src/main/jniLibs/arm64-v8a/libpldroidplayer.so differ diff --git a/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi-v7a/libpldroidplayer.so b/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi-v7a/libpldroidplayer.so old mode 100644 new mode 100755 index 8d72925..06ca5fb Binary files a/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi-v7a/libpldroidplayer.so and b/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi-v7a/libpldroidplayer.so differ diff --git a/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi/libpldroidplayer.so b/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi/libpldroidplayer.so old mode 100644 new mode 100755 index 525bc53..02e0270 Binary files a/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi/libpldroidplayer.so and b/PLDroidPlayerDemo/app/src/main/jniLibs/armeabi/libpldroidplayer.so differ diff --git a/PLDroidPlayerDemo/app/src/main/jniLibs/x86/libpldroidplayer.so b/PLDroidPlayerDemo/app/src/main/jniLibs/x86/libpldroidplayer.so old mode 100644 new mode 100755 index 70610ff..0e5e164 Binary files a/PLDroidPlayerDemo/app/src/main/jniLibs/x86/libpldroidplayer.so and b/PLDroidPlayerDemo/app/src/main/jniLibs/x86/libpldroidplayer.so differ diff --git a/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/audio.jpg b/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/audio.jpg deleted file mode 100644 index 44550fd..0000000 Binary files a/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/audio.jpg and /dev/null differ diff --git a/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/ic_rotate_right.png b/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/ic_rotate_right.png deleted file mode 100644 index abc89e6..0000000 Binary files a/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/ic_rotate_right.png and /dev/null differ diff --git a/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/ic_switch_screen.png b/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/ic_switch_screen.png deleted file mode 100644 index 52601b0..0000000 Binary files a/PLDroidPlayerDemo/app/src/main/res/drawable-hdpi/ic_switch_screen.png and /dev/null differ diff --git a/PLDroidPlayerDemo/app/src/main/res/layout/activity_audio_player.xml b/PLDroidPlayerDemo/app/src/main/res/layout/activity_audio_player.xml index e93ac3b..28bf83a 100644 --- a/PLDroidPlayerDemo/app/src/main/res/layout/activity_audio_player.xml +++ b/PLDroidPlayerDemo/app/src/main/res/layout/activity_audio_player.xml @@ -1,40 +1,53 @@ - - + - + + + + + +