视频直播,点播,悬浮小窗口播放demo (使用阿里云sdk)

使用阿里云直播sdk demo ,支持直播,点播 视频直播与悬浮窗小窗口无缝衔接切换(一般拉流格式 rtmp m3u8等)

直播点播提示,监听,各种状态返回以及各种提示

视频播放基础库 支持全屏,快进,手势基础操作

全局视频小窗口 权限判断,高斯模糊背景(可以加深颜色)

飘心效果,还有列表单例播放视频 ,列表全屏视频,视频支持手势,进度,亮度,声音

demo下载地址

效果图:

007.gif
002.gif
003.gif
004.gif
005.gif
006.gif
007.gif

这里对直播与悬浮窗直播无缝切换说一下实现原理:

1 ,直播组件做成单例,通过addview的形式,在悬浮窗的framelayout上增加直播view,切换正常模式也是在原页面留下的framelayout站位增加这个直播view

/**
 * Description:初始化直播弹窗工具
 * Created by PangHaHa on 18-7-18.
 * Copyright (c) 2018 PangHaHa All rights reserved.
 */
public class LiveUtils {

    //布局参数.
    private static WindowManager.LayoutParams params;
    //实例化的WindowManager.
    private static WindowManager windowManager;
    private static int statusBarHeight =-1;
    private static FrameLayout toucherLayout;
    private static ImageView imageViewClose;

    private static int count = 0;//点击次数
    private static long firstClick = 0;//第一次点击时间
    private static long secondClick = 0;//第二次点击时间

    private static float start_X = 0;
    private static float start_Y = 0;


    // 记录上次移动的位置
    private static float lastX = 0;
    private static float lastY = 0;
    private static int offset;
    // 是否是移动事件
    private static boolean isMoved = false;
    /**
     * 两次点击时间间隔,单位毫秒
     */
    private static final int totalTime = 1000;

    private static boolean isInit = true;


    public static void initLive(final Context context ){
        try {

            windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
            //赋值WindowManager&LayoutParam.
            params = new WindowManager.LayoutParams();
            //设置type.系统提示型窗口,一般都在应用程序窗口之上.
            if (Build.VERSION.SDK_INT >= 26) {//8.0新特性
                params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
            } else {
                params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
            }
            //设置效果为背景透明.
            params.format = PixelFormat.RGBA_8888;
            //设置flags.不可聚焦及不可使用按钮对悬浮窗进行操控.
            params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
            //设置窗口坐标参考系
            params.gravity = Gravity.LEFT | Gravity.TOP;
            //用于检测状态栏高度.
            int resourceId = context.getResources().getIdentifier("status_bar_height",
                    "dimen","android");
            if (resourceId > 0) {
                statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
            }
            offset = DensityUtil.dp2px(context, 2);//移动偏移量
            //设置原点
            params.x = getScreenWidth(context) - DensityUtil.dp2px(context, 170);
            params.y = getScreenHeight(context) - DensityUtil.dp2px(context, 100+72) ;
            //设置悬浮窗口长宽数据.
            params.width = DensityUtil.dp2px(context, 180);
            params.height = DensityUtil.dp2px(context, 100);

            //获取浮动窗口视图所在布局.
            toucherLayout = new FrameLayout(context);
            AliyunVodPlayerView playerView = new AliyunVodPlayerView(context);
            playerView.initVideoView(true);
            playerView.setAutoPlay(true);
            playerView.setKeepScreenOn(true);
            playerView.setTitleBarCanShow(false);
            playerView.setControlBarCanShow(false);
            AliyunLocalSource.AliyunLocalSourceBuilder alsb = new AliyunLocalSource.AliyunLocalSourceBuilder();
            alsb.setSource("http://ivi.bupt.edu.cn/hls/cctv3hd.m3u8");
            AliyunLocalSource localSource = alsb.build();
            playerView.setLiveSource(localSource);

            toucherLayout.addView(playerView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT));

            imageViewClose = new ImageView(context);
            imageViewClose.setImageResource(R.drawable.icon_close);
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                    DensityUtil.dp2px(context, 16), DensityUtil.dp2px(context, 16));
            layoutParams.gravity = Gravity.TOP | Gravity.RIGHT;
            layoutParams.rightMargin = DensityUtil.dp2px(context, 3);
            layoutParams.topMargin = DensityUtil.dp2px(context, 3);
            imageViewClose.setLayoutParams(layoutParams);

            toucherLayout.addView(imageViewClose,layoutParams);


            //添加toucherlayout
            if(isInit) {
                windowManager.addView(toucherLayout,params);
            } else {
                windowManager.updateViewLayout(toucherLayout,params);
            }

            //主动计算出当前View的宽高信息.
            toucherLayout.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

            //处理touch
            toucherLayout.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View view, MotionEvent event) {

                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            isMoved = false;
                            // 记录按下位置
                            lastX = event.getRawX();
                            lastY = event.getRawY();

                            start_X = event.getRawX();
                            start_Y = event.getRawY();
                            break;
                        case MotionEvent.ACTION_MOVE:
                            isMoved = true;
                            // 记录移动后的位置
                            float moveX = event.getRawX();
                            float moveY = event.getRawY();
                            // 获取当前窗口的布局属性, 添加偏移量, 并更新界面, 实现移动
                            params.x += (int) (moveX - lastX);
                            params.y += (int) (moveY - lastY);
                            if (toucherLayout!=null){
                                windowManager.updateViewLayout(toucherLayout,params);
                            }
                            lastX = moveX;
                            lastY = moveY;
                            break;
                        case MotionEvent.ACTION_UP:

                            float fmoveX = event.getRawX();
                            float fmoveY = event.getRawY();

                            if (Math.abs(fmoveX-start_X)<offset && Math.abs(fmoveY-start_Y)<offset){
                                isMoved = false;

                                Intent intent = new Intent(context,MainActivity.class);
                                context.startActivity(intent);


                            }else {
                                isMoved = true;
                            }
                            break;
                    }
                        // 如果是移动事件, 则消费掉; 如果不是, 则由其他处理, 比如点击
                    return isMoved;
                }

            });

            //删除
            imageViewClose.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    remove(context);
                }
            });
        }catch (Exception e){
            e.printStackTrace();
        }

        isInit = false;
    }

    private static void remove(Context context) {
        if(windowManager != null && toucherLayout != null) {
            windowManager.removeView(toucherLayout);
            isInit = true;
        }
    }

    /**
     * 获取屏幕宽度(px)
     */
    private static int getScreenWidth(Context context) {
        return context.getResources().getDisplayMetrics().widthPixels;
    }
    /**
     * 获取屏幕高度(px)
     */
    private static int getScreenHeight(Context context){
        return context.getResources().getDisplayMetrics().heightPixels;
    }

}

2,对弹窗初始化时权限的判断 魅族和小米需要特殊处理

 private void showLiveWindow() {
        if (Build.VERSION.SDK_INT >= 23) {
            if (!Settings.canDrawOverlays(getContext())) {
                //没有悬浮窗权限,跳转申请
                Toast.makeText(getApplicationContext(), "请开启悬浮窗权限", Toast.LENGTH_LONG).show();
                //魅族不支持直接打开应用设置
                if (!MEIZU.isMeizuFlymeOS()) {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
                    startActivityForResult(intent, 0);
                } else {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivityForResult(intent, 0);
                }
            } else {
                LiveUtils.initLive(MainActivity.this);
                finish();
            }
        } else {
            //6.0以下 只有MUI会修改权限
            if (MIUI.rom()) {
                if (PermissionUtils.hasPermission(getContext())) {
                    LiveUtils.initLive(MainActivity.this);
                    finish();
                } else {
                    MIUI.req(getContext());
                }
            } else {
                LiveUtils.initLive(MainActivity.this);
                finish();
            }
        }

    }

下面是判断各种rom机型工具类


import android.os.Build;
import android.text.TextUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class OsUtils {

    public static final String ROM_MIUI = "MIUI";
    public static final String ROM_EMUI = "EMUI";
    public static final String ROM_FLYME = "FLYME";
    public static final String ROM_OPPO = "OPPO";
    public static final String ROM_SMARTISAN = "SMARTISAN";
    public static final String ROM_VIVO = "VIVO";
    public static final String ROM_QIKU = "QIKU";

    private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name";
    private static final String KEY_VERSION_EMUI = "ro.build.version.emui";
    private static final String KEY_VERSION_OPPO = "ro.build.version.opporom";
    private static final String KEY_VERSION_SMARTISAN = "ro.smartisan.version";
    private static final String KEY_VERSION_VIVO = "ro.vivo.os.version";

    private static String sName;
    private static String sVersion;

    public static boolean isEmui() {
        return check(ROM_EMUI);
    }

    public static boolean isMiui() {
        return check(ROM_MIUI);
    }

    public static boolean isVivo() {
        return check(ROM_VIVO);
    }

    public static boolean isOppo() {
        return check(ROM_OPPO);
    }

    public static boolean isFlyme() {
        return check(ROM_FLYME);
    }

    public static boolean is360() {
        return check(ROM_QIKU) || check("360");
    }

    public static boolean isSmartisan() {
        return check(ROM_SMARTISAN);
    }

    public static String getName() {
        if (sName == null) {
            check("");
        }
        return sName;
    }

    public static String getVersion() {
        if (sVersion == null) {
            check("");
        }
        return sVersion;
    }

    public static boolean check(String rom) {
        if (sName != null) {
            return sName.equals(rom);
        }

        if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_MIUI))) {
            sName = ROM_MIUI;
        } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_EMUI))) {
            sName = ROM_EMUI;
        } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_OPPO))) {
            sName = ROM_OPPO;
        } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_VIVO))) {
            sName = ROM_VIVO;
        } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_SMARTISAN))) {
            sName = ROM_SMARTISAN;
        } else {
            sVersion = Build.DISPLAY;
            if (sVersion.toUpperCase().contains(ROM_FLYME)) {
                sName = ROM_FLYME;
            } else {
                sVersion = Build.UNKNOWN;
                sName = Build.MANUFACTURER.toUpperCase();
            }
        }
        return sName.equals(rom);
    }

    public static String getProp(String name) {
        String line = null;
        BufferedReader input = null;
        try {
            Process p = Runtime.getRuntime().exec("getprop " + name);
            input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
            line = input.readLine();
            input.close();
        } catch (IOException ex) {
            return null;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return line;
    }
}

github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容