前言:

现在网络上有youtube、facebook、twitch、快手等主流的直播平台,他们基本都开放了直播API让你的应用能够进行集成以实现帮助你的用户去管理自己的直播活动,你可以在用户授权后调用API帮助用户创建直播活动并得到该直播活动的推流地址信息,之后你就可以通过开源的或者市面上例如腾讯直播推流SDK等推流工具以实现帮助用户将你应用的直播画面推流到直播平台的能力,该系列文章不重点介绍推流工具,重点讲解各大平台的直播API集成方式,并show you the code,本文介绍youtube 的直播API(Youtube Data API)。

准备工作:

先附上Youtube Data API的官方介绍。

1、youtube目前隶属于google,所以youtube的API是google API的一部分,你首先得有个google账号;

2、创建google应用并启用youtube data api;

3、你的应用需要集成google的账号授权SDK,用来拉起google账号的登录授权页面获得用户授权的,需要获取对用户youtube视频的操作权限,scope(权限范围)要给https://www.googleapis.com/auth/youtube和https://www.googleapis.com/auth/youtube.force-ssl,这里需要大家对OAuth2.0的流程有一定理解;

4、先调通用户授权流程获得具有直播API操作权限的token后即可调用youtube直播API或者你自己对youtube直播API的后台封装。

直播API封装:

代码部分使用JAVA语言实现(代码只提供主流程代码),

1、创建频道(broadcast)、创建视频流对象(stream)、并且将频道和流绑定在一起,并且获得推流地址信息,我将这几步写到一起,下面是整体流程:

public YoutubeChannelVo addYoutubeChannel(YoutubeChannelDto params, String userId) {
        log.info("[RtmpManageServiceImpl.createYoutubeBroadcast]enter service.");
        YoutubeChannelVo result = new YoutubeChannelVo();
        try {
            packageBoardCastData(params, result, userId);
            packageStreamInfoData(params, result, userId);
            String authorization = "Bearer " + params.getGoogleToken();
            String url = "https://www.googleapis.com/youtube/v3/liveBroadcasts/bind?part=id,contentDetails&";
            url = url + "id=" + result.getLiveBroadCastId();
            url = url + "&streamId=" + result.getStreamId();
            String resultStr = HttpUtils.post(url, "{}", authorization);
            log.info("[{}]", resultStr);
        } catch (NorthException ex) {
            log.error("[RtmpManageServiceImpl.createYoutubeBroadcast] error msg:[{}].", ex.getErrorMsg());
            throw ex;
        } catch (Exception ex) {
            log.error("[RtmpManageServiceImpl.createYoutubeBroadcast] error msg:[{}].", ex.getMessage());
            throw new NorthException(NorthErrorEnum.SERVER_INTERNAL_ERROR);
        }
        log.info("[RtmpManageServiceImpl.createYoutubeBroadcast]out service.");
        return result;
    }

参数对象中主要参数有:

YoutubeChannelDto 参数对象

创建频道具体实现:

private void packageBoardCastData(YoutubeChannelDto params, YoutubeChannelVo result, String userId) throws ParseException, NorthException {
        Date currentTime = DateUtils.getCurrentDateTime();
        // 封装请求参数
        Map<String, Object> data = new HashMap<>();
        Map<String, Object> snippet = new HashMap<>();
        snippet.put("title", params.getTitle());
        snippet.put("actualStartTime", params.getStartTime());
        snippet.put("scheduledStartTime", params.getStartTime());
        if (!StringUtils.isBlank(params.getEndTime())) {
            snippet.put("scheduledEndTime", params.getEndTime());
        }
        data.put("snippet", snippet);
        Map<String, Object> status = new HashMap<>();
        status.put("privacyStatus", params.getPrivacy());
        data.put("status", status);
        Map<String, Object> contentDetails = new HashMap<>();
        contentDetails.put("projection", params.getProjection());
        data.put("contentDetails", contentDetails);
        JSONObject jsonObject = new JSONObject(data);
        String jsonStr = jsonObject.toString();
        String authorization = "Bearer " + params.getGoogleToken();
        String url = "https://www.googleapis.com/youtube/v3/liveBroadcasts?part=id,snippet,status,contentDetails";
        String resultStr = HttpUtils.post(url, jsonStr, authorization);
        log.info("[{}]", resultStr);
        result.setLiveBroadCastId(object.getString("id"));
    }

创建流具体实现:

private void packageStreamInfoData(YoutubeChannelDto params, YoutubeChannelVo result, String userId) throws ParseException {
        Date currentTime = DateUtils.getCurrentDateTime();
        // 封装请求参数
        Map<String, Object> data = new HashMap<>();
        Map<String, Object> snippet = new HashMap<>();
        snippet.put("title", params.getTitle());
        data.put("snippet", snippet);
        Map<String, Object> cdn = new HashMap<>();
        Map<String, Object> ingestionInfo = new HashMap<>();
        ingestionInfo.put("streamName", params.getTitle());
        ingestionInfo.put("ingestionAddress", "rtmp://a.rtmp.youtube.com/live2");
        cdn.put("ingestionInfo", ingestionInfo);
        cdn.put("resolution", params.getResolution());
        cdn.put("ingestionType", "rtmp");
        cdn.put("frameRate", params.getFrameRate());
        data.put("cdn", cdn);

        JSONObject jsonObject = new JSONObject(data);
        String jsonStr = jsonObject.toString();
        String authorization = "Bearer " + params.getGoogleToken();
        String url = "https://www.googleapis.com/youtube/v3/liveStreams?part=snippet,cdn";
        String resultStr = HttpUtils.post(url, jsonStr, authorization);
        JSONObject object = JSON.parseObject(resultStr);
        result.setStreamId(object.getString("id"));
        // 这里其实就得到了推流地址
        result.setSecureStreamUrl(e.getAddr() + "/" + e.getSecret());
    }

然后根据前两步获得的broadcastId和streamId调用绑定接口将频道和流进行绑定:

String authorization = "Bearer " + params.getGoogleToken();
            String url = "https://www.googleapis.com/youtube/v3/liveBroadcasts/bind?part=id,contentDetails&";
            url = url + "id=" + result.getLiveBroadCastId();
            url = url + "&streamId=" + result.getStreamId();
            String resultStr = HttpUtils.post(url, "{}", authorization);

通过前面的流程后台已经为我们自己的应用得到了一个推流地址,大概长这样rtmps://http://a.rtmps.youtube.com/live2/q29f-3eda-ymvp-v8zq-413d,“rtmps://http://a.rtmps.youtube.com/live2”这一部分是固定的,后面是每次创建流生成的流ID,两部分拼接在一起就是完整的推流地址,这时候我们的应用得到这个地址后就可以用集成的推流SDK往这个地址推直播视频流了,但是距离用户在youtube上看到你的直播视频流还差一步,需要对频道进行状态切换,推流之后频道的状态默认是testing,需要检查频道状态和stream状态并最终将频道状态成功切换成live后,用户才能看到直播。

2、直播频道状态检测与切换该步骤除了平台token外需要传入频道和流的ID(UpdateVBroadcastStatusDto),应用开始推流之后再调用一次下面后台封装的流程即可从后台完成状态的检测与切换:

public UpdateVBroadcastStatusVo updateBroadcastStatus(UpdateVBroadcastStatusDto params) {
        log.info("[RtmpManageServiceImpl.queryLiveStreamStatus]enter service.params:[{}]", params);
        UpdateVBroadcastStatusVo result = new UpdateVBroadcastStatusVo();
        result.setStreamId(params.getStreamId());
        result.setLiveBroadCastId(params.getLiveBroadCastId());
        try {
            String authorization = "Bearer " + params.getGoogleToken();
            String url = "https://www.googleapis.com/youtube/v3/liveStreams?part=id,status&";
            url = url + "id=" + params.getStreamId();
            Map<String, String> header = new HashMap<>();
            header.put("Authorization", authorization);
            String resultStr = HttpUtils.get(url, header);
            log.info("[RtmpManageServiceImpl.queryLiveStreamStatus] result: [{}]", resultStr);
            JSONObject resultObject = JSON.parseObject(resultStr);
            int times = 0;
            if (resultObject.getJSONArray("items") != null && resultObject.getJSONArray("items").size() > 0) {
                String status = resultObject.getJSONArray("items").getJSONObject(0).getJSONObject("status").getString("streamStatus");
                while (!"active".equals(status)) {
                    Thread.sleep(1000);
                    times += 1;
                    log.info("[RtmpManageServiceImpl.queryLiveStreamStatus]times:[{}].", times);
                    if (times > 60) {
                        break;
                    }
                    String resultStr2 = HttpUtils.get(url, header);
                    log.info("[{}]", resultStr2);
                    JSONObject resultObject2 = JSON.parseObject(resultStr2);
                    status = resultObject2.getJSONArray("items").getJSONObject(0).getJSONObject("status").getString("streamStatus");
                }
                log.info("[RtmpManageServiceImpl.queryLiveStreamStatus] active status:[{}]", status);
            }
            times = 0;
            url = "https://www.googleapis.com/youtube/v3/liveBroadcasts/transition?part=id,snippet,contentDetails,status&";
            url = url + "id=" + params.getLiveBroadCastId() + "&";
            url = url + "broadcastStatus=" + "testing";
            resultStr = HttpUtils.post(url, "{}", authorization);
            log.info("[{}]", resultStr);

            url = "https://www.googleapis.com/youtube/v3/liveBroadcasts?part=id,status&";
            url = url + "id=" + params.getLiveBroadCastId();
            resultStr = HttpUtils.get(url, header);
            log.info("[{}]", resultStr);
            resultObject = JSON.parseObject(resultStr);
            if (resultObject.getJSONArray("items") != null && resultObject.getJSONArray("items").size() > 0) {
                String status = resultObject.getJSONArray("items").getJSONObject(0).getJSONObject("status").getString("lifeCycleStatus");
                while (!"testing".equals(status)) {
                    Thread.sleep(1000);
                    times += 1;
                    log.info("[RtmpManageServiceImpl.queryLiveStreamStatus]times:[{}].", times);
                    if (times > 60) {
                        break;
                    }
                    String resultStr2 = HttpUtils.get(url, header);
                    log.info("[{}]", resultStr2);
                    JSONObject resultObject2 = JSON.parseObject(resultStr2);
                    status = resultObject2.getJSONArray("items").getJSONObject(0).getJSONObject("status").getString("lifeCycleStatus");
                }
                log.info("[RtmpManageServiceImpl.queryLiveStreamStatus] testing status:[{}]", status);
            }
            url = "https://www.googleapis.com/youtube/v3/liveBroadcasts/transition?part=id,snippet,contentDetails,status&";
            url = url + "id=" + params.getLiveBroadCastId() + "&";
            url = url + "broadcastStatus=" + "live";
            resultStr = HttpUtils.post(url, "{}", authorization);
            log.info("[{}]", resultStr);
        } catch (NorthException ex) {
            log.error("[RtmpManageServiceImpl.queryLiveStreamStatus] error msg:[{}].", ex.getErrorMsg());
            throw ex;
        } catch (Exception ex) {
            log.error("[RtmpManageServiceImpl.queryLiveStreamStatus] error msg:[{}].", ex.getMessage());
            throw new NorthException(NorthErrorEnum.SERVER_INTERNAL_ERROR);
        }
        log.info("[RtmpManageServiceImpl.queryLiveStreamStatus]out service.");
        return result;
    }