安卓 即构科技直播使用阿里云鉴权切换流地址

      即构科技集成直播后,需求中需要拉流端(观众)切换到阿里云的流地址,而不是用即构返回的即构流地址,原因是即构的流比较贵,阿里云的收费非常的便宜,差距大概是阿里云的四倍吧,所以考虑到用前端进行流地址的切换,直播推流决定着观众的拉流,也就是说,主播直播的时候推的是什么流,观众拉的就是什么流了,所以在推流的时候进行切换流地址,(没有让即构在返回列表里返回多个流地址,据说即构那边是可以配置的),我是直接与后台对接,把后台拿到的即构流、阿里云流等都通过一个接口返回给我,我自己截取替换流地址;对于阿里云的流地址的使用,除了替换还有进行鉴权,下面看看具体的文档:

阿里云鉴权的说明:https://www.alibabacloud.com/help/zh/doc-detail/97943.htm?spm=a2c63.p38356.b99.29.37182eb8uQa4rL

代码示例:https://www.alibabacloud.com/help/zh/doc-detail/57388.htm?spm=a2c63.p38356.879954.22.117d7e67Z9OUO1#concept-b2p-mfd-2fb

https://www.alibabacloud.com/help/zh/doc-detail/66016.htm?spm=a2c63.p38356.b99.72.3e31zGVxzGVxCj

https://help.aliyun.com/knowledge_list/49528.html

即构相关链接:

https://doc.zego.im/API/ZegoLiveRoom/Android/html/com/zego/zegoliveroom/ZegoLiveRoom.html#startPublishing-java.lang.String-java.lang.String-int-java.lang.String-

https://doc.zego.im/API/ZegoLiveRoom/Android/html/com/zego/zegoliveroom/ZegoLiveRoom.html#startPublishing2-java.lang.String-java.lang.String-int-int-

https://doc.zego.im/API/ZegoLiveRoom/Android/html/com/zego/zegoliveroom/ZegoLiveRoom.html#startPlayingStream-java.lang.String-java.lang.Object-

https://doc.zego.im/CN/303.html
startPublishing 开始推流 
startPublishing2 开始推流,可推两路流 
startPlayingStream 开始拉流 

 

https://doc.zego.im/CN/491.html#3
如何自定义指定的CDN拉流

 

如何获取转码推流地址

  • 推流地址规则

    直播只支持RTMP格式推流。

    推流地址格式为RTMP://推流域名/AppName/StreamName?鉴权串

  • 示例

    例如,推流域名是push.aliyunlive.com,AppName为app,StreamName为stream,鉴权key是123,则推流地址为RTMP://push.aliyunlive.com/app/stream?auth_key=timestamp-rand-uid-md5hash

 

  • 示例

    例如,播流域名是play.aliyunlive.com,AppName为app,StreamName为stream,鉴权key是456,使用sd模板,拼接地址如下所示:

    • RTMP协议地址rtmp://play.aliyunlive.com/app/stream_sd?auth_key=timestamp-rand-uid-md5hash
    • FLV协议地址http://play.aliyunlive.com/app/stream_sd.flv?auth_key=timestamp-rand-uid-md5hash
    • HLS协议地址http://play.aliyunlive.com/app/stream_sd.m3u8?auth_key=timestamp-rand-uid-md5hash

鉴权串字段对照,参见鉴权地址Democode

 
字段描述
timestamp失效时间,整形正数,固定长度 10,1970 年 1 月 1 日以来的秒数。用来控制失效时间,10 位整数,有效时间 1800s
rand随机数,建议使用UUID(不能包含中划线“-”,例如: 477b3bbc253f467b8def6711128c7bec 格式)
uid暂未使用(设置成 0 即可)
md5hash通过 md5 算法计算出的验证串,数字和小写英文字母混合(0-9和a-z),固定长度 32

 

鉴权算法

未鉴权URL: http://DomainName/AppName/StreamName.flv

  • 鉴权后的URL:http://DomainName/Filename?auth_key=timestamp-rand-0-md5hash

参数说明

  • timestamp:失效时间,整形正数,固定长度 10,1970 年 1 月 1 日以来的秒数。用来控制失效时间。

  • rand:随机数,一般设成 0。

  • md5hash:通过md5算法计算出的验证串,算法(假设用户的密钥为 123456:md5hash=md5(timestamp-rand-0-123456)。

鉴权示例

用户给 a.com 这个域名配置了一个鉴权密钥:xyz,希望鉴权有效时间为:2017-07-28 05:43:20,准备用rtmp://video-center.alivecdn.com/live/abc?vhost=a.com进行推流,相应的鉴权计算逻辑为:

推流

  • 获取推流uri:/live/abc

  • 获取 timestamp:过期时间 2017-07-28 05:43:200 的unixtimestamp为:1501191800。

  • 组装加密字符串:/live/abc-1501191800-0-0-xyz

  • 计算加密后的字符串:md5 (“/live/abc-1501191800-0-0-xyz”) = 7856896499b661b1123dca82d7406aa3。

  • 拼接加密后的推流URL:rtmp://video- center.alivecdn.com/live/abc?vhost=a.com?auth_key=1501191800-0-0-7856896499b661b1123dca82d7406aa3

播放

播放的计算方法与推流类似,以 httpflv 播放地址为例。

  • 未鉴权的 URL 为:http:// a.com/live/abc.flv

  • 获取播放URL:/live/abc.flv

  • 获取 timestamp:过期时间 2017-07-28 05:43:200 的 unixtimestamp 为:1501191800。

  • 组装加密字符串:/live/abc.flv-1501191800-0-0-xyz

  • 计算加密后的字符串:md5(“/live/abc.flv-1501191800-0-0-xyz”) = b022238fd0cd1c8efae2ba84fc0c9119。

  • 拼接加密后的推流URL:http://a.com/live/abc.flv?auth_key=1501191800-0-0-b022238fd0cd1c8efae2ba84fc0c9119

  • 设置方法:可以在直播控制台进行设置,控制台中鉴权计算器,可以方便的生成鉴权 URL。详情参考 直播鉴权

关键说明上面已经贴出,下面来看看代码部分:

1.在BasePublishActivity中设置流地址的切换:

(替换前)

 /**
     * 推流成功.
     */
    protected void handlePublishSucc(String streamID, HashMap<String, Object> info) {
      
//        ViewLive viewLivePublish = getViewLiveByStreamID(streamID);
//        List<String> listUrls = getShareUrlList(info);
//        if (viewLivePublish != null && listUrls.size() >= 2) {
//            // 设置分享连接
//            viewLivePublish.setListShareUrls(listUrls);
//
//            // 将流地址发送给房间观众
//            Map<String, String> mapUrls = new HashMap<>();
//            mapUrls.put(Constants.FIRST_ANCHOR, String.valueOf(true));
//            mapUrls.put(Constants.KEY_HLS, listUrls.get(0));
//            mapUrls.put(Constants.KEY_RTMP, listUrls.get(1));
//            Gson gson = new Gson();
//            String json = gson.toJson(mapUrls);
//            mZegoLiveRoom.updateStreamExtraInfo(json);
//        }

        super.handlePublishSucc(streamID);
    }

上述代码注释取消就可以了,可以看到

mapUrls.put(Constants.KEY_RTMP, url1);

就是RTMP直播时候提供给观众的流地址,在这里进行替换。

(替换后)

 /**
     * 推流成功.
     */
    protected void handlePublishSucc(String streamID, HashMap<String, Object> info) {
        getRtmpUrl(streamID,info);


        super.handlePublishSucc(streamID);
    }
/**
     * 获取直播推流的流地址
     */
    private void getRtmpUrl(final String streamId, final HashMap<String, Object> info) {

        HttpParam param = new HttpParam();
        param.put("streamId", streamId);
        HttpLoader.getInstance(this).post(UrlConstant.RTMPURL, param, UrlConstant.USER_EDIT_RECORDS_REQUEST_CODE, new HttpLoader.HttpListener() {
            @Override
            public void onSuccess(int resultCode, String result) {
                RtmpUrlBean entity = JsonUtils.toEntity(result, RtmpUrlBean.class);
                if (entity != null) {
                    if(!TextUtils.isEmpty(entity.getData())){
                        //截取流地址
                        String rtmpUrl = entity.getData();
                        if(rtmpUrl.indexOf(",") != -1)//是否包含,
                        {
//                            System.out.println("包含该字符串");
                            String[] splitUrl = rtmpUrl.split(",");
                            String url0 = splitUrl[0];
                            url1 = splitUrl[1];
                            String url2 = splitUrl[2];
                        }

                        //
                    }
                    ViewLive viewLivePublish = getViewLiveByStreamID(streamId);
                    List<String> listUrls = getShareUrlList(info);
                    if (viewLivePublish != null && listUrls.size() >= 2) {
                        // 设置分享连接
                        viewLivePublish.setListShareUrls(listUrls);

                        // 将流地址发送给房间观众
                        Map<String, String> mapUrls = new HashMap<>();
                        mapUrls.put(Constants.FIRST_ANCHOR, String.valueOf(true));
                        mapUrls.put(Constants.KEY_HLS, listUrls.get(0));
                        if(TextUtils.isEmpty(entity.getData())){
                            mapUrls.put(Constants.KEY_RTMP, listUrls.get(1));
                        }else {
                            mapUrls.put(Constants.KEY_RTMP, url1);
                        }

                        Gson gson = new Gson();
                        String json = gson.toJson(mapUrls);
                        mZegoLiveRoom.updateStreamExtraInfo(json);
                    }

                }
            }

            @Override
            public void onfailed(int resultCode, String error) {
            }
        });
    }

可以看到,后台返回的字符串通过逗号截取后进行替换地址,这样就可以,接着就是要进行阿里云流地址的鉴权了:

2.在BaseLiveActivity中设置阿里云鉴权:

protected void publishStream(){}方法中修改,将原来的startPublishing修改成startPublishing2,把相应的参数加上:

 protected void publishStream() {

        if (TextUtils.isEmpty(mPublishStreamID)) {
            return;
        }
        if (isStreamExisted(mPublishStreamID)) {
            Toast.makeText(this, "流已存在", Toast.LENGTH_LONG).show();
            return;
        }

        ViewLive freeViewLive = getFreeViewLive();
        if (freeViewLive == null) {
            return;
        }

        // 设置流信息
        freeViewLive.setStreamID(mPublishStreamID);
        freeViewLive.setPublishView(true);

        // 初始化配置信息, 混流模式使用
        initPublishConfigs();

        // 输出发布状态
        recordLog(MY_SELF + ": start publishing(" + mPublishStreamID + ")");

        // 设置水印
//        mZegoLiveRoom.setWaterMarkImagePath("asset:watermark.png");
//        Rect rect = new Rect();
//        rect.left = 50;
//        rect.top = 20;
//        rect.right = 200;
//        rect.bottom = 170;
//        mZegoLiveRoom.setPreviewWaterMarkRect(rect);
//        mZegoLiveRoom.setPublishWaterMarkRect(rect);

        // 开启流量自动控制
        int properties = ZegoConstants.ZegoTrafficControlProperty.ZEGOAPI_TRAFFIC_FPS
                | ZegoConstants.ZegoTrafficControlProperty.ZEGOAPI_TRAFFIC_RESOLUTION;
        mZegoLiveRoom.enableTrafficControl(properties, true);

        //开启双声道
        mZegoLiveRoom.setAudioChannelCount(2);

        // 开始播放
//        if(PreferenceUtil.getInstance().getUseExternalRender(false)){
//          if(videoRenderer == null){
//              videoRenderer = new VideoRenderer();
//          }
//         开启外部渲染
//         videoRenderer.init();
//         videoRenderer.setRendererView(freeViewLive.getTextureView());
//         mZegoLiveRoom.setZegoExternalRenderCallback(videoRenderer);
//        }else{
//         }
        mZegoLiveRoom.setPreviewView(freeViewLive.getTextureView());
        mZegoLiveRoom.startPreview();


        if (PreferenceUtil.getInstance().getVideoFilter(false)) {
            mZegoLiveRoom.setFilter(ZegoFilter.Wine, ZegoConstants.PublishChannelIndex.MAIN);
        }

        mZegoLiveRoom.enableMic(mEnableMic);
        mZegoLiveRoom.enableCamera(mEnableCamera);
       // 阿里云主播推流未鉴权
//        mZegoLiveRoom.startPublishing(mPublishStreamID, mPublishTitle, mPublishFlag);
        //阿里云主播推流鉴权
        //传入自定义参数,即传入应用名称和流名称
        String yuMing = "ceshipushs_a.a.sallsmeanalysispxy.xyzk";
        //时间戳,有效时间
//        long time = (System.currentTimeMillis() + 1800) / 1000;
        long time = (System.currentTimeMillis() / 1000) + 1800;
        //加密key,即直播后台鉴权里面自行设置
        String key = "AldNSFA6h9";
        String strpush = "/" + "live" + "/" + mPublishStreamID + "-" + time + "-0-0-" + key;
        // 里面的直播推流中心服务器域名、vhost域名可根据自身实际情况进行设置
        String pushurl = "rtmp://ceshi.a.sallysispxy.xyzk/" + "lives" + "/" + mPublishStreamID + "?auth_key=" + time + "-0-0-" + MD5Util.MD5(strpush);
        String rtmpurl = "rtmp://" + yuMing + "/" + "live" + "/" + mPublishStreamID + "?auth_key=" + time + "-0-0-" + MD5Util.MD5(strpush);
        LogUtils.e("KING: " + pushurl);
        LogUtils.e("KING: " + rtmpurl);
        LogUtils.e("KING: " + strpush);
        mZegoLiveRoom.startPublishing2(mPublishStreamID, mPublishTitle, mPublishFlag,"1024","auth_key=" + time + "-0-0-" + MD5Util.MD5(strpush),ZegoConstants.PublishChannelIndex.MAIN);
        //阿里云主播推流鉴权

        mZegoLiveRoom.setPreviewViewMode(ZegoVideoViewMode.ScaleAspectFill);
    }

上述代码中涉及到设置时间戳:(设置十位数的时间戳)

a.十位数的UNIX时间戳:

Long newTime = System.currentTimeMillis();
String time = newTime.toString().substring(0, 10); 

 或:

long time = (System.currentTimeMillis() / 1000) + 1800;

或:


       long timeStampSec = System.currentTimeMillis()/1000;
       String timestamp = String.format("%010d", timeStampSec);

或自定义工具类,写在方法中:

 /**
     * 将时间转换成时间戳
     * 设置即构、阿里云需要的时间戳(秒)
     * @return counter
     */
    public static String dataToStamp() {
        long time = System.currentTimeMillis();
        time += 30*1000*60;//在当前系统时间加30分钟,有效时间30分钟
        String res = "";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date data = simpleDateFormat.parse(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time));
            long ts = data.getTime();//得出的是毫秒,阿里云需要的是秒,所以除以1000
            ts = ts/1000;
            res = String.valueOf(ts);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return res;
    }

//        long time = (System.currentTimeMillis() + 1800) / 1000;默认是13位的。

3.MD5工具类:(最下面有视频直播Url鉴权工具类

package com.southsummer.goddessplan.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Util {
    public final static String MD5(String s) {
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f','g','h','i','j','k','l','m','n',
                'o','p','q','r','s','t','u','v','w','x',
                'y','z'};
        try {
            byte[] btInput = s.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * @Description: 32位小写MD5
     */
    public static String parseStrToMd5L32(String str) {
        String reStr = null;
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(str.getBytes());
            StringBuffer stringBuffer = new StringBuffer();
            for (byte b : bytes) {
                int bt = b & 0xff;
                if (bt < 16) {
                    stringBuffer.append(0);
                }
                stringBuffer.append(Integer.toHexString(bt));
            }
            reStr = stringBuffer.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return reStr;
    }

    /**
     * @param str
     * @Description: 32位大写MD5
     */
    public static String parseStrToMd5U32(String str) {
        String reStr = parseStrToMd5L32(str);
        if (reStr != null) {
            reStr = reStr.toUpperCase();
        }
        return reStr;
    }

    /**
     * @param str
     * @Description: 16位小写MD5
     */
    public static String parseStrToMd5U16(String str) {
        String reStr = parseStrToMd5L32(str);
        if (reStr != null) {
            reStr = reStr.toUpperCase().substring(8, 24);
        }
        return reStr;
    }

    /**
     * @param str
     * @Description: 16位大写MD5
     */
    public static String parseStrToMd5L16(String str) {
        String reStr = parseStrToMd5L32(str);
        if (reStr != null) {
            reStr = reStr.substring(8, 24);
        }
        return reStr;
    }
}

 

4.接下来就是在拉流的时候鉴权了,需求是拉流和推流都需要鉴权的

在BasePlayActivity中设置:

    /**
     * 观众登录房间成功.
     */
    protected void handleAudienceLoginRoomSuccess(ZegoStreamInfo[] zegoStreamInfos) {
        // 播放房间的流
        if (zegoStreamInfos != null && zegoStreamInfos.length > 0) {
            for (int i = 0; i < zegoStreamInfos.length; i++) {
                String streamId = zegoStreamInfos[i].streamID;
                LogUtils.e("流附加信息:  " + zegoStreamInfos[i].extraInfo);
                LogUtils.e("流附加信息: 长度: " + zegoStreamInfos.length);
                ZegoStreamExtraPlayInfo info = JsonUtils.toEntity(zegoStreamInfos[i].extraInfo, ZegoStreamExtraPlayInfo.class);
                LogUtils.w("流附加信息:" + info.rtmpUrls+"------"+info.flvUrls+"------"+mOldSavedStreamList);
                if (mOldSavedStreamList != null && mOldSavedStreamList.contains(streamId)) {
                    LogUtils.w("BasePlayAct" + "Has quick start, ignore");
                    mOldSavedStreamList.remove(streamId);
                } else {

                    startPlay(streamId, info);
                }
            }

            if (mOldSavedStreamList != null && mOldSavedStreamList.size() > 0) {
                for (String streamId : mOldSavedStreamList) {
                    LogUtils.w("BasePlayAct" + "Remove timeout stream id: " + streamId);
                    stopPlay(streamId);
                }
                mOldSavedStreamList.clear();
            }
        }

        // 分享主播视频
        if (zegoStreamInfos != null && zegoStreamInfos.length > 0) {
            for (ZegoStreamInfo info : zegoStreamInfos) {

                ViewLive viewLive = getViewLiveByStreamID(info.streamID);
                final HashMap<String, String> mapInfo =
                        (new Gson()).fromJson(info.extraInfo, new TypeToken<HashMap<String, String>>() {
                        }.getType());

                if (viewLive != null && mapInfo != null) {
                    boolean firstAnchor = Boolean.valueOf(mapInfo.get(Constants.FIRST_ANCHOR));
                    if (firstAnchor) {
                        List<String> listUrls = new ArrayList<>();
                        listUrls.add(mapInfo.get(Constants.KEY_HLS));
                        listUrls.add(mapInfo.get(Constants.KEY_RTMP));
                        viewLive.setListShareUrls(listUrls);
                        break;
                    }

                }
            }
        }

        // 打印log
        recordLog(MY_SELF + ": onLoginRoom success(" + mRoomID + "), streamCounts:" + zegoStreamInfos.length);
    }

根据https://doc.zego.im/CN/491.html#3  可以看出,在包房房间流的时候进行流地址的设置,调用startPlayingStream的接口可以传入完整的自定义拉流地址,参考如下:

ZegoStreamExtraPlayInfo info = new ZegoStreamExtraPlayInfo();
   info.params = "";

    //以下rtmp或flv的拉流方式,两个选择一个就可以
   info.rtmpUrls = new String[]{"xxx", "yyy"}; //数组中的每个值都是完整的rtmp拉流,如果有鉴权参数的,将鉴权参数带上
   info.flvUrls = new String[]{"xxx", "yyy"}; //数组中的每个值都是完整的flv拉流地址,如果有鉴权参数的,将鉴权参数带上

   mZegoLiveRoom.startPlayingStream("streamID",inview, info);
   //这里的streamID一定不能为空,可以自定义一个,inview是外部指定的渲染视图

这里的鉴权和推流的鉴权是否一致,还要等验证完才可以下结论。

鉴权注意推流、拉流的细节:

推流

  • 获取推流uri:/live/abc

  • 获取 timestamp:过期时间 2017-07-28 05:43:200 的unixtimestamp为:1501191800。

  • 组装加密字符串:/live/abc-1501191800-0-0-xyz

  • 计算加密后的字符串:md5 (“/live/abc-1501191800-0-0-xyz”) = 7856896499b661b1123dca82d7406aa3。

  • 拼接加密后的推流URL:rtmp://video- center.alivecdn.com/live/abc?vhost=a.com?auth_key=1501191800-0-0-7856896499b661b1123dca82d7406aa3

播放

播放的计算方法与推流类似,以 httpflv 播放地址为例。

  • 未鉴权的 URL 为:http:// a.com/live/abc.flv

  • 获取播放URL:/live/abc.flv

  • 获取 timestamp:过期时间 2017-07-28 05:43:200 的 unixtimestamp 为:1501191800。

  • 组装加密字符串:/live/abc.flv-1501191800-0-0-xyz

  • 计算加密后的字符串:md5(“/live/abc.flv-1501191800-0-0-xyz”) = b022238fd0cd1c8efae2ba84fc0c9119。

  • 拼接加密后的推流URL:http://a.com/live/abc.flv?auth_key=1501191800-0-0-b022238fd0cd1c8efae2ba84fc0c9119

  • 设置方法:可以在直播控制台进行设置,控制台中鉴权计算器,可以方便的生成鉴权 URL。详情参考 直播鉴权

视频直播Url鉴权工具类:

import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Component;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;

@Component
@SuppressWarnings({"FieldCanBeLocal", "SameParameterValue", "WeakerAccess"})
class LivingUrlUtil {
    //app名称 自己定义
    private final String appName = "dkcj";
    //房间名 自己定义 随便取
    private final String streamName = "room1";
    //鉴权失效的超时时间(分)
    private final Integer overTime = 30;
    //推流域名 阿里云配置的推流域名
    private final String pushDomain = "zbvip.push.haic666.com";
    //拉流域名 阿里云配置的拉流域名
    private final String pullDomain = "zbvip.pull.haic666.com";
    //uid   暂时没用,设置为0
    private final String uid = "0";
    //密钥 阿里配置的鉴权key
    private final String privateKey = "qihan888";
    //加密随机字段,也可以设置为0
    private final String randomNumber = RandomStringUtils.random(5, "1234567890");


    String getPushUrl() {
        String pathModel = "/%s/%s";
        return getUrl(pathModel, pushDomain, "rtmp://");
    }

    String getPullUrl() {
        String pathModel = "/%s/%s";
        return getUrl(pathModel, pullDomain, "rtmp://");
    }

    String getPullUrlFlv() {
        String pathModel = "/%s/%s.flv";
        return getUrl(pathModel, pullDomain, "http://");
    }

    String getPullUrlM3u8() {
        String pathModel = "/%s/%s.m3u8";
        return getUrl(pathModel, pullDomain, "http://");
    }

    private String getUrl(String pathModel, String domain, String origin) {
        //当前时间戳+超时时间 ,用来生成鉴权字串
        Long timestampSecond = Instant.now().getEpochSecond() + overTime * 60;
        System.out.println(timestampSecond);
        //路径原始地址 用于生成鉴权字串
        String pathUrl = String.format(pathModel, appName, streamName);
        //拉流原始地址meu8版  http://{pullDomain}/{AppName}/{StreamName}
        String urlBase = String.format("%s%s%s", origin, domain, pathUrl);
        //获取推流鉴权字段
        String pushMd5Hash = getSString(pathUrl, timestampSecond);
        //格式化推流最终地址  最终推流地址模板 pushUrlBase?auth_key=timestamp-rand-uid-md5hash
        return String.format("%s?auth_key=%s-%s-%s-%s", urlBase, timestampSecond, randomNumber, uid, pushMd5Hash);
    }

    private String getSString(String pushUrl, Long timestampSecond) {
        //格式化鉴权字符串 鉴权原始字符串 URI-timestamp-rand-uid-PrivateKey
        String pushSstring = String.format("%s-%s-%s-%s-%s", pushUrl, timestampSecond, randomNumber, uid, privateKey);
        //鉴权字符串md5
        return getMd5(pushSstring).toLowerCase();
    }

    private static String getMd5(String s) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(s.getBytes());
            BigInteger HashInt = new BigInteger(1, messageDigest.digest());
            return String.format("%1$032X", HashInt);
        } catch (NoSuchAlgorithmException lException) {
            throw new RuntimeException(lException);
        }
    }
}

 随机数工具类:

package com.southsummer.goddessplan.utils;


import android.graphics.Color;

import java.util.HashSet;

import java.util.Iterator;

import java.util.Random;

import java.util.Set;


/**
 * 工具类-随机数
 */

public class RandomUtil {
    private static final String ALL_CHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String LETTER_CHAR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final String NUMBER_CHAR = "0123456789";


    /**
     * 获取从a至z,长度为length随机数
     *
     * @return
     */

    public static String getRandomStr(int length) {

        String base = "abcdefghijklmnopqrstuvwxyz";

        Random random = new Random();

        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < length; i++) {

            int number = random.nextInt(base.length());

            sb.append(base.charAt(number));

        }

        return sb.toString();

    }


    /**
     * 获取范围内int值
     *
     * @param
     * @return
     */
    public static int getRandomRange(int max, int min) {

        return (int) (Math.random() * (max - min) + min);

    }


    /**
     * 获取随机长度随机字符
     *
     * @param length base
     * @return
     */

    public static String getRandomString(int length, String base) { // length表示生成字符串的长度

        Random random = new Random();

        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < length; i++) {

            int number = random.nextInt(base.length());

            sb.append(base.charAt(number));

        }

        return sb.toString();

    }


    /**
     * 获取随机长度随机字符
     *
     * @param length
     * @return
     */

    public static String getRandomString(int length) { // length表示生成字符串的长度

        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

        Random random = new Random();

        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < length; i++) {

            int number = random.nextInt(base.length());

            sb.append(base.charAt(number));

        }

        return sb.toString();

    }


    /**
     * 获取随机长度随机数字
     *
     * @param length
     * @return
     */

    public static String getRandomNumString(int length) { // length表示生成字符串的长度

        String base = "0123456789";

        Random random = new Random();

        StringBuffer sb = new StringBuffer();

        for (int i = 0; i < length; i++) {

            int number = random.nextInt(base.length());

            sb.append(base.charAt(number));

        }

        return sb.toString();

    }


    /**
     * 返回随机数组
     *
     * @param start 开始值
     * @param end   结束值
     * @return
     */

    public static int[] getRangRandom(int start, int end) {

        return getRangRandom(start, end, end - start + 1);

    }


    /**
     * 返回指定范围指定个数的不重复随机数。
     *
     * @param start
     * @param end
     * @param num
     * @return
     */

    public static int[] getRangRandom(int start, int end, int num) {


        int length = end - start + 1;

        // 参数不合法

        if (length < 1 || num > length) {

            return null;

        } else {

            int[] numbers = new int[length];

            int[] result = new int[num];

            // 循环赋初始值

            for (int i = 0; i < length; i++) {

                numbers[i] = i + start;

            }

            Random random = new Random();

            // 取randomMax次数

            for (int i = 0; i < num; i++) {

                // 随机获取取数的位置

                int m = random.nextInt(length - i) + i;

                result[i] = numbers[m];

                // 交换位置

                int temp = numbers[m];

                numbers[m] = numbers[i];

                numbers[i] = temp;

            }

            return result;

        }

    }



    /*

     * 生成6位随机数验证码

     */

    public static String code() {

        Set<Integer> set = GetRandomNumber();

        Iterator<Integer> iterator = set.iterator();

        String temp = "";

        while (iterator.hasNext()) {

            temp += iterator.next();

        }

        return temp;

    }


    public static Set<Integer> GetRandomNumber() {

        Set<Integer> set = new HashSet<Integer>();

        Random random = new Random();

        while (set.size() < 6) {

            set.add(random.nextInt(10));

        }

        return set;

    }

    /**
     * 获取定长的随机数,只包含数字
     *
     * @param length 随机数长度
     * @return
     */
    public static String generateNumberString(int length) {
        StringBuffer sb = new StringBuffer();
        Random random = new Random();
        random.
        for (int i = 0; i < length; i++) {
            sb.append(NUMBER_CHAR.charAt(random.nextInt(NUMBER_CHAR.length())));
        }
        return sb.toString();
    }

    /**
     * 获取定长的随机数,包含大小写字母
     *
     * @param length 随机数长度
     * @return
     */
    public static String generateMixString(int length) {
        StringBuffer sb = new StringBuffer();
        Random random = new Random();
        for (int i = 0; i < length; i++) {
            sb.append(LETTER_CHAR.charAt(random.nextInt(LETTER_CHAR.length())));
        }
        return sb.toString();
    }


    /**
     * 获取定长的随机数,只包含小写字母
     *
     * @param length 随机数长度
     * @return
     */
    public static String generateLowerString(int length) {
        return generateMixString(length).toLowerCase();
    }

    /**
     * 获取定长的随机数,只包含大写字母
     *
     * @param length 随机数长度
     * @return
     */
    public static String generateUpperString(int length) {
        return generateMixString(length).toUpperCase();
    }

    static Random rd = new Random();

    /**
     * 产生1-9之间随机数
     */

    public static int gtRdNum() {

        return rd.nextInt(9) + 1; //(0-9内)+1 变相的变成了 1-10内

    }

    /**
     * 产生(+、-、*、/)之间随机运算符
     */

    public static String gtRdEx() {

        int ex = rd.nextInt(4); // 0+、1-、2*、3/

        if (ex == 0) {
            return "+";
        }

        if (ex == 1) {
            return "-";
        }

        if (ex == 2) {
            return "*";
        }

        if (ex == 3) {
            return "/";
        }

        return "";

    }

    /**
     * 递归获取整数
     */

    public static String gtZs() {

        String str = gtEx();

        if (".0".equals(str.substring(str.length() - 2))) {

            return str;

        } else {

            System.out.println("[递归] " + str);

            return gtZs();

        }

    }


    /**
     * 获取3个随机数(1-9),进行随机加减乘数运行(+、-、*、/),返回整数运算结果的表达式
     */

    public static String gtEx() {

        String str = gtRdNum() + gtRdEx() + gtRdNum() + gtRdEx() + gtRdNum();


        double ts = 0.00;

        //优先计算乘除(第一个运算符)

        if ("*".equals(str.substring(1, 2)) || "/".equals(str.substring(1, 2))) {

            if ("*".equals(str.substring(1, 2))) {

                ts = Integer.parseInt(str.substring(0, 1)) * Integer.parseInt(str.substring(2, 3));

            } else {

                ts = Integer.parseInt(str.substring(0, 1)) / Integer.parseInt(str.substring(2, 3));

            }


            //第二个运算符

            if ("+".equals(str.substring(3, 4))) {

                ts = ts + Integer.parseInt(str.substring(4, 5));

            } else if ("-".equals(str.substring(3, 4))) {

                ts = ts - Integer.parseInt(str.substring(4, 5));

            } else if ("*".equals(str.substring(3, 4))) {

                ts = ts * Integer.parseInt(str.substring(4, 5));

            } else if ("/".equals(str.substring(3, 4))) {

                ts = ts / Integer.parseInt(str.substring(4, 5));

            }

        } else {

            //优先计算乘除(第二个运算符)

            if ("*".equals(str.substring(3, 4)) || "/".equals(str.substring(3, 4))) {

                if ("*".equals(str.substring(3, 4))) {

                    ts = Integer.parseInt(str.substring(2, 3)) * Integer.parseInt(str.substring(4, 5));

                } else {

                    ts = Integer.parseInt(str.substring(2, 3)) / Integer.parseInt(str.substring(4, 5));

                }


                //第二个运算符

                if ("+".equals(str.substring(1, 2))) {

                    ts = Integer.parseInt(str.substring(0, 1)) + ts;

                } else if ("-".equals(str.substring(1, 2))) {

                    ts = Integer.parseInt(str.substring(0, 1)) - ts;

                } else if ("*".equals(str.substring(1, 2))) {

                    ts = Integer.parseInt(str.substring(0, 1)) * ts;

                } else if ("/".equals(str.substring(1, 2))) {

                    ts = Integer.parseInt(str.substring(0, 1)) / ts;

                }

            } else {

                //第一个运算符

                if ("+".equals(str.substring(1, 2))) {

                    ts = Integer.parseInt(str.substring(0, 1)) + Integer.parseInt(str.substring(2, 3));

                } else {

                    ts = Integer.parseInt(str.substring(0, 1)) - Integer.parseInt(str.substring(2, 3));

                }

                //第二个运算符

                if ("+".equals(str.substring(3, 4))) {

                    ts = ts + Integer.parseInt(str.substring(4, 5));

                } else {

                    ts = ts - Integer.parseInt(str.substring(4, 5));

                }

            }

        }

        return str + "=" + ts;

    }

    /**
     * 获取指定长度的16进制字符串.
     */

    public static String randomHexStr(int len) {

        try {

            StringBuffer result = new StringBuffer();

            for (int i = 0; i < len; i++) {

                //随机生成0-15的数值并转换成16进制

                result.append(Integer.toHexString(new Random().nextInt(16)));

            }

            return result.toString().toUpperCase();

        } catch (Exception e) {

            System.out.println("获取16进制字符串异常,返回默认...");

            return "00CCCC";

        }

    }


    // 随机颜色
    // Random random = new Random();
    // int ranColor = 0xff000000 | random.nextInt(0x00ffffff);
    // holder.iv_dynamic_pic.setBackgroundColor(ranColor);

    // 随机颜色
    // Random random = new Random();
    // int []ranColor ={0x00FFFFCC,0xCCFFFF,0xff000000,0x00ffff00,0x00FFFFCC,0x00ff0000};
    // 0xff000000 | random.nextInt(0x00ffffff);
    // int randomcolor =random.nextInt(ranColor.length);
    // int color = Color.argb(1, random.nextInt(255) / 255, random.nextInt(255) / 255, random.nextInt(255) / 255);
    // holder.iv_dynamic_pic.setBackgroundColor(color);

    // 随机颜色
    // Random random = new Random();
    // #FFFFF0//int ranColor = 0xff000000 | random.nextInt(0x00ffffff);
    // int []ranColor ={0xffFFFFF0,0xffF5FFFA, 0xffFFE7B, 0xffF0F8FF, 0xffEECFA1, 0xffF5FFFA,0xffFFF0F5,0xffEEE9E9};
    // 0xff000000 | random.nextInt(0x00ffffff);   int randomcolor =random.nextInt(ranColor.length);
    // int color = Color.argb(1, random.nextInt(255) / 255, random.nextInt(255) / 255, random.nextInt(255) / 255);
    // holder.iv_dynamic_pic.setBackgroundColor(ranColor[randomcolor]);

    // 随机颜色
//    Random myRandom=new Random();
//    int ranColor = 0xff000000 | mRandom.nextInt(0x00ffffff);
//    Random random = new Random();
//    int[] ranColor = {0xff000000, 0x00ff00ff, 0xff000000, 0x00ffff00, 0x00ffffff, 0x00ff0000};//0xff000000 | random.nextInt(0x00ffffff);
//     for(int i = 0;i<ranColor.length;i++)
//    {
//        holder.iv_dynamic_pic.setBackgroundColor(ranColor[i]);
//    }

//    Random random = new Random();
//    int r = random.nextInt(256);
//    int g = random.nextInt(256);
//    int b = random.nextInt(256);
//    textView.setTextColor(Color.rgb(r,g,b));


    /**
     * 获取十六进制的颜色代码.例如  "#5A6677"
     * 分别取R、G、B的随机值,然后加起来即可
     *
     * @return String
     */
    public static String getRandColor() {
        String R, G, B;
        Random random = new Random();
        R = Integer.toHexString(random.nextInt(256)).toUpperCase();
        G = Integer.toHexString(random.nextInt(256)).toUpperCase();
        B = Integer.toHexString(random.nextInt(256)).toUpperCase();

        R = R.length() == 1 ? "0" + R : R;
        G = G.length() == 1 ? "0" + G : G;
        B = B.length() == 1 ? "0" + B : B;

        return R + G + B;
    }



    /**
     * 获取十六进制的颜色代码.例如  "#6E36B4" , For HTML ,
     *
     * @return String
     */
    public static String getRandColorCode() {
        String r, g, b;
        Random random = new Random();
        r = Integer.toHexString(random.nextInt(256)).toUpperCase();
        g = Integer.toHexString(random.nextInt(256)).toUpperCase();
        b = Integer.toHexString(random.nextInt(256)).toUpperCase();

        r = r.length() == 1 ? "0" + r : r;
        g = g.length() == 1 ? "0" + g : g;
        b = b.length() == 1 ? "0" + b : b;

        return r + g + b;
    }

    private static final int[] prefix = {1, 2, 3, 4, 5, 6, 7, 8, 9};
//    /**
//     * 随机产生最大为18位的long型数据(long型数据的最大值是9223372036854775807,共有19位)	 * 	 * @param digit	 *            用户指定随机数据的位数
//     */
//    public static long randomLong(int digit) {
//        if (digit >= 19 || digit <= 0)
//            throw new IllegalArgumentException("digit should between 1 and 18(1<=digit<=18)");
//        String s = RandomUtil.randomNumeric(digit - 1);
//        return Long.parseLong(getPrefix() + s);
//    }


    private static int getDigit(int max) {
        return rd.nextInt(max + 1);
    }

    /**
     * 保证第一位不是零	 * 	 * @return
     */
    private static String getPrefix() {
        return prefix[rd.nextInt(9)] + "";
    }


}

 

如有错误之处,请多多指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值