Android实现悬浮播放器

来源:互联网 发布:世界局势 知乎 编辑:程序博客网 时间:2024/06/05 08:00
1. 实现的大概流程:
首先一个本地的视频正在播放时(MovieActivity类),程序里用变量保存当前播放视频的URI和MIME类型,当点击某个按钮时,会先把当前视频的播放位置保存到数据库里,然后开始进入悬浮框初始化(显示位置、大小、属性等),悬浮框里包含了VideoView类,这个类就是用于播放视频,将之前保存的数据(URI等)对其赋值,然后关闭当前播放视频的窗口,这样浮动视频框就出来了!同样当悬浮视频切换到本地原生视频播放器时,也会保留当前播放视频的播放位置,在重新进入MovieActivity类时,将这保留的最新数据进行对其赋值,这样就可以继续播放了!

2. MainActivity.java

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sendBroadcast(new Intent(Mp3BroadCastReceiver.ACTION_MOVIE_START));
finish();
}

3. Mp3BroadCastReceiver.java
public class Mp3BroadCastReceiver extends BroadcastReceiver {

public static final String ACTION_MOVIE_START = "com.example.lcn_louis.START";

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ACTION_MOVIE_START)) {

Intent mIntent = new Intent("createUI");
mIntent.setClass(context, MediaPlaybackService.class);
context.startService(mIntent);
}
}
}

4. MediaPlaybackService.java
public class MediaPlaybackService extends Service {



@Override
public void onCreate() {
super.onCreate();

}

public ViewGroup fView;
MyFloatView sFloatView;

private void createView(Context context) {
if (fView != null) {
return;
}

fView = (ViewGroup) View.inflate(context, R.layout.activity_main, null);
// 显示myFloatView图像
sFloatView = new MyFloatView(fView);
sFloatView.bindViewListener();
sFloatView.showLayoutView();

}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action = intent.getAction();

String cmd = intent.getStringExtra("command");

if ("createUI".equals(action)) {
createView(this);
} else if ("removeUI".equals(action)) {

fView = null;
sFloatView = null;
}
}

return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}

5. MyFloatView.java
public class MyFloatView implements OnCompletionListener, OnErrorListener,
OnInfoListener, OnPreparedListener, OnSeekCompleteListener,
OnVideoSizeChangedListener, SurfaceHolder.Callback,
android.view.View.OnClickListener
{
private float mTouchStartX;
private float mTouchStartY;
private float x;
private float y;
ViewGroup mlayoutView;
Context context;
Display currentDisplay;
SurfaceView surfaceView;
SurfaceHolder surfaceHolder;
Button mButton;
MediaPlayer mediaPlayer;// 使用的是MediaPlayer来播放视频
int videoWidth = 0; // 视频的宽度,初始化,后边会对其进行赋值
int videoHeight = 0; // 同上
boolean readyToPlayer = false;
public final static String LOGCAT = "CUSTOM_VIDEO_PLAYER";

public static final String ACTION_DESTROY_MOVIE = "com.example.lcn_louis.Destory";

public MyFloatView(ViewGroup layoutView)
{
// TODO Auto-generated constructor stub
mlayoutView = layoutView;
context = layoutView.getContext();
layoutView.setOnTouchListener(new OnTouchListener()
{

@Override
public boolean onTouch(View paramView, MotionEvent paramMotionEvent)
{
// TODO Auto-generated method stub
onTouchEvent(paramMotionEvent);
return false;
}
});

initWindow();
}

public View getLayoutView()
{
return mlayoutView;
}

public void onResume()
{
}

public void initWindow()
{

// 获取WindowManager
wm = (WindowManager) context.getApplicationContext().getSystemService(
context.WINDOW_SERVICE);
// 设置LayoutParams(全局变量)相关参数
// wmParams = ((MyApplication)getApplication()).getMywmParams();
wmParams = new WindowManager.LayoutParams();
/**
* 以下都是WindowManager.LayoutParams的相关属性 具体用途可参考SDK文档
*/
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; // 设置window type
// 设置图片格式,效果为背景透明
wmParams.format = PixelFormat.TRANSPARENT;
// 设置Window flag

wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
/*
* 下面的flags属性的效果形同“锁定”。 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
* wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL |
* LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE;
*/
wmParams.gravity = Gravity.LEFT | Gravity.TOP; // 调整悬浮窗口至左上角
// 以屏幕左上角为原点,设置x、y初始值
currentDisplay = wm.getDefaultDisplay();
WIDTH = currentDisplay.getWidth();
HEIGHT = currentDisplay.getHeight();
wmParams.x = 0;
wmParams.y = 0;
// 设置悬浮窗口长宽数据
wmParams.width = VIEW_WIDTH;
wmParams.height = VIEW_HEIGHT;
}

static int VIEW_WIDTH = LayoutParams.WRAP_CONTENT, VIEW_HEIGHT = LayoutParams.WRAP_CONTENT;

public void bindViewListener()
{
initialUI();
}

private void initialUI()
{
// setContentView(R.layout.main);

// 关于SurfaceView和Surfaceolder可以查看文档
surfaceView = (SurfaceView) mlayoutView.findViewById(R.id.myView);
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnCompletionListener(this);
mediaPlayer.setOnErrorListener(this);
mediaPlayer.setOnPreparedListener(this);
mediaPlayer.setOnSeekCompleteListener(this);
mediaPlayer.setOnVideoSizeChangedListener(this);
String filePath = "/storage/sdcard0/testVideo.mp4";
// 本地地址和网络地址都可以
try
{
mediaPlayer.setDataSource(filePath);
} catch (IllegalArgumentException e)
{
// TODO: handle exception
Log.v(LOGCAT, e.getMessage());
onExit();
} catch (IllegalStateException e)
{
Log.v(LOGCAT, e.getMessage());
onExit();
} catch (IOException e)
{
Log.v(LOGCAT, e.getMessage());
onExit();
}

IntentFilter filter = new IntentFilter(ACTION_DESTROY_MOVIE);
context.registerReceiver(sReceiver, filter);

}

BroadcastReceiver sReceiver = new BroadcastReceiver()
{

@Override
public void onReceive(Context context, Intent intent)
{
// TODO Auto-generated method stub
onExit();
}
};

public void onExit()
{

try
{
wm.removeView(mlayoutView);
Intent mIntent = new Intent("removeUI");
mIntent.setClass(context, MediaPlaybackService.class);
context.startService(mIntent);
context.unregisterReceiver(sReceiver);
mediaPlayer.pause();
mediaPlayer.stop();
mediaPlayer.release();
} catch (Exception e)
{
// TODO: handle exception
}
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height)
{
Log.v(LOGCAT, "surfaceChanged Called");

}

@Override
public void surfaceCreated(SurfaceHolder holder)
{
Log.v(LOGCAT, "suc calles");
mediaPlayer.setDisplay(holder);// 若无次句,将只有声音而无图像
try
{
mediaPlayer.prepare();
} catch (IllegalStateException e)
{
onExit();
} catch (IOException e)
{
onExit();
}

}

@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
Log.v(LOGCAT, "surfaceDestroyed Called");

}

@Override
public void onVideoSizeChanged(MediaPlayer arg0, int arg1, int arg2)
{
Log.v(LOGCAT, "onVideoSizeChanged Called");

}

@Override
public void onSeekComplete(MediaPlayer mp)
{
Log.v(LOGCAT, "onSeekComplete Called");

}

@Override
public void onPrepared(MediaPlayer mp)
{
Log.v(LOGCAT, "onPrepared Called");
videoWidth = mp.getVideoWidth();
videoHeight = mp.getVideoHeight();
/** 这一步为videod的高宽赋值,将其值控制在可控的范围之内,在VideoView的源码中也有相关的代码,有兴趣可以一看 */
if (videoWidth > currentDisplay.getWidth()
|| videoHeight > currentDisplay.getHeight())
{
float heightRatio = (float) videoHeight
/ (float) currentDisplay.getHeight();
float widthRatio = (float) videoWidth
/ (float) currentDisplay.getWidth();
if (heightRatio > 1 || widthRatio > 1)
{
if (heightRatio > widthRatio)
{
videoHeight = (int) Math.ceil((float) videoHeight
/ (float) heightRatio);
videoWidth = (int) Math.ceil((float) videoWidth
/ (float) heightRatio);
} else
{
videoHeight = (int) Math.ceil((float) videoHeight
/ (float) widthRatio);
videoWidth = (int) Math.ceil((float) videoWidth
/ (float) widthRatio);
}
}
}

videoWidth = 400;
videoHeight = 300;

// 设置悬浮窗口长宽数据
wmParams.width = VIEW_WIDTH = videoWidth;
wmParams.height = VIEW_HEIGHT = videoHeight + 36;

mButton = new Button(context);
mButton.setBackgroundResource(R.drawable.ic_action_scale);
RelativeLayout.LayoutParams sParams = new RelativeLayout.LayoutParams(
android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT,
android.widget.RelativeLayout.LayoutParams.WRAP_CONTENT);
sParams.setMargins(videoWidth-100, videoHeight-80, 0, 0);
mButton.setLayoutParams(sParams);
mButton.setOnClickListener(new OnClickListener()
{

@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
onExit();
}
});
mlayoutView.addView(mButton);

surfaceView.setLayoutParams(new RelativeLayout.LayoutParams(videoWidth,
videoHeight));
mp.start();

}

@Override
public boolean onInfo(MediaPlayer arg0, int whatInfo, int extra)
{
if (whatInfo == MediaPlayer.MEDIA_INFO_BAD_INTERLEAVING)
{
Log.v(LOGCAT, "Media Info, Media Info Bad Interleaving " + extra);
} else if (whatInfo == MediaPlayer.MEDIA_INFO_NOT_SEEKABLE)
{
Log.v(LOGCAT, "Media Info, Media Info Not Seekable " + extra);
} else if (whatInfo == MediaPlayer.MEDIA_INFO_UNKNOWN)
{
Log.v(LOGCAT, "Media Info, Media Info Unknown " + extra);
} else if (whatInfo == MediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING)
{
Log.v(LOGCAT, "MediaInfo, Media Info Video Track Lagging " + extra);
} else if (whatInfo == MediaPlayer.MEDIA_INFO_METADATA_UPDATE)
{
Log.v(LOGCAT, "MediaInfo, Media Info Metadata Update " + extra);
}

return false;
}

@Override
public boolean onError(MediaPlayer arg0, int arg1, int arg2)
{
Log.v(LOGCAT, "onError Called");
if (arg1 == MediaPlayer.MEDIA_ERROR_SERVER_DIED)
{
Log.v(LOGCAT, "Media Error, Server Died " + arg2);
} else if (arg1 == MediaPlayer.MEDIA_ERROR_UNKNOWN)
{
Log.v(LOGCAT, "Media Error, Error Unknown " + arg2);
}

return false;
}

@Override
public void onCompletion(MediaPlayer arg0)
{
Log.v(LOGCAT, "onCompletion Called");
onExit();

}

public void showLayoutView()
{
wm.addView(mlayoutView, wmParams);

}

public static int WIDTH = 1024, HEIGHT = 552;
public static final int left = -VIEW_WIDTH / 2, top = 0, right = WIDTH
- VIEW_WIDTH / 2, bottom = HEIGHT - VIEW_HEIGHT / 2;
private WindowManager wm = null;
private WindowManager.LayoutParams wmParams = null;
private float mLastTouchY = 0;
int rangeOut_H = -1, rangeOut_V = -1;

public boolean onTouchEvent(MotionEvent event)
{
// 获取相对屏幕的坐标,即以屏幕左上角为原点
x = event.getRawX();
y = event.getRawY(); // 25是系统状态栏的高度
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
// 获取相对View的坐标,即以此View左上角为原点
mTouchStartX = event.getX();
mTouchStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (mLastTouchY > 110)
{
mTouchStartX = event.getX();
mTouchStartY = event.getY();
mLastTouchY = 0;
}
int positionX = (int) (x - mTouchStartX);
int positionY = (int) (y - mTouchStartY);

if (positionX < left || positionX > right)
{// check borad just
// reset
rangeOut_H = positionX < left ? left : right;
}
if (positionY < top || positionY > bottom)
{// check borad just
// reset
rangeOut_V = positionY < top ? top : bottom;
}

updateViewPosition();
break;

case MotionEvent.ACTION_UP:

if (rangeOut_H != -1)
{
x = rangeOut_H + mTouchStartX;
}
if (rangeOut_V != -1)
{
y = rangeOut_V + mTouchStartY;
}

updateViewPosition();
mTouchStartX = mTouchStartY = 0;
rangeOut_H = rangeOut_V = -1;
break;
default:
mTouchStartX = mTouchStartY = 0;
rangeOut_H = rangeOut_V = -1;
break;
}
return true;
}

private void updateViewPosition()
{
// 更新浮动窗口位置参数
wmParams.x = (int) (x - mTouchStartX);
wmParams.y = (int) (y - mTouchStartY);
wm.updateViewLayout(mlayoutView, wmParams);

}

@Override
public void onClick(View v)
{
// TODO Auto-generated method stub

}

}
6. Activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<SurfaceView
android:id="@+id/myView"
android:layout_width="400dp"
android:layout_height="300dp"
android:layout_marginLeft="4dip"
android:layout_marginTop="4dip" />

</RelativeLayout>
7. AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />


<service android:name=".MediaPlaybackService"></service>

<!-- 监听SD卡 -->
<receiver android:name=".Mp3BroadCastReceiver" >
<intent-filter android:priority="1000" >
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<action android:name="android.intent.action.MEDIA_EJECT" />
<action android:name="android.intent.action.MEDIA_SCANNER_FINISHED" />

<data android:scheme="file" />
</intent-filter>
<intent-filter android:priority="1000" >
<action android:name="com.example.lcn_louis.START" />
</intent-filter>
<intent-filter android:priority="1000">
<action android:name="com.example.lcn_louis.DESTROY"/>
</intent-filter>

</receiver>

原创粉丝点击