// +---------------------------------------------------------------------- namespace App\Services; use AlibabaCloud\Tea\Exception\TeaUnableRetryError; use AlibabaCloud\SDK\Dysmsapi\V20170525\Dysmsapi; use App\Models\LiveModel; use App\Models\MemberModel; use App\Models\VideoCollectModel; use App\Services\Api\MemberCollectService; use App\Services\Api\VideoCollectService; use Darabonba\OpenApi\Models\Config; use AlibabaCloud\SDK\Dysmsapi\V20170525\Models\SendSmsRequest; use AlibabaCloud\Tea\Utils\Utils\RuntimeOptions; use Illuminate\Support\Facades\DB; /** * 在线直播服务管理-服务类 * @author laravel开发员 * @since 2020/11/11 * Class LiveService * @package App\Services */ class LiveService extends BaseService { // 静态对象 protected static $instance = null; /** * 构造函数 * @author laravel开发员 * @since 2020/11/11 * ConfigService constructor. */ public function __construct() { $this->model = new LiveModel(); } /** * 静态入口 * @return SmsService|static|null */ public static function make() { if (!self::$instance) { self::$instance = new static(); } return self::$instance; } /** * 列表数据 * @param $params * @param int $pageSize * @return array */ public function getDataList($params, $pageSize = 18, $field = '', $userId=0) { $where = ['a.mark' => 1,'a.status'=>1,'b.mark'=>1]; $field = $field? $field : 'lev_a.*'; $order = 'rand()'; $model = $this->model->with(['member'])->from('live as a') ->leftJoin('member as b', 'b.id', '=', 'a.user_id') ->where($where) ->where(function ($query) use ($params) { $type = isset($params['type']) ? $params['type'] : 0; if ($type > 0) { $query->where('a.type', $type); } $categoryId = isset($params['category_id']) ? $params['category_id'] : 0; if ($categoryId > 0) { $query->where('a.category_id', $categoryId); } $uid = isset($params['user_id']) ? $params['user_id'] : 0; if ($uid > 0) { $query->where('a.user_id', $uid); } }) ->where(function ($query) use ($params) { $keyword = isset($params['kw']) ? $params['kw'] : ''; if ($keyword) { $query->where('a.title', 'like', "%{$keyword}%") ->orWhere('a.description', 'like', "%{$keyword}%") ->orWhere('b.nickname', 'like', "%{$keyword}%"); } })->where(function ($query) use ($userId) { if ($userId) { $query->where('a.visible_users', '=', '') ->orWhere('a.visible_users', 'like',"%{$userId},%") ->orWhere('a.manage_users', 'like',"%{$userId},%"); } }); // 推荐的数据 $countModel = clone $model; $total = $countModel->where(function($query) use($params, $userId){ // 推荐视频数据 $isRecommend = isset($params['is_recommend']) ? $params['is_recommend'] : 0; if ($isRecommend > 0) { $recommendData = VideoCollectService::make()->getRecommendData($userId); $uids = isset($recommendData['uids'])? $recommendData['uids'] : []; // 按用户推荐 $category = isset($recommendData['category'])? $recommendData['category'] : []; // 按标签推荐 if($uids){ $query->orWhere(function($query) use($uids){ $query->whereIn('a.user_id', $uids); }); } if($category){ $query->orWhere(function($query) use($category){ $query->whereIn('a.category_id', $category); }); } } })->count('a.id'); if($total > 0){ // 关联推荐数据 $list = $countModel->selectRaw($field) ->orderByRaw($order) ->paginate($pageSize > 0 ? $pageSize : 9999999); }else{ // 默认推荐数据 $list = $model->selectRaw($field) ->orderByRaw($order) ->paginate($pageSize > 0 ? $pageSize : 9999999); } $list = $list ? $list->toArray() : []; if ($list && $list['data']) { foreach ($list['data'] as &$item) { $item['time_text'] = isset($item['create_time']) ? dateFormat($item['create_time'], 'Y-m-d H:i') : ''; $member = isset($item['member'])? $item['member'] : []; if($member){ $member['avatar'] = isset($member['avatar']) && $member['avatar']? get_image_url($member['avatar']) : get_image_url('/images/member/logo.png'); } $item['like_num'] = isset($item['like_num']) && $item['like_num']? intval($item['like_num']) : 0; $item['views'] = isset($item['views']) && $item['views']? intval($item['views']) : 0; $item['member'] = $member; } } return [ 'pageSize' => $pageSize, 'total' => isset($list['total']) ? $list['total'] : 0, 'list' => isset($list['data']) ? $list['data'] : [] ]; } /** * 详情 * @param $id * @param $userId * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null */ public function getInfo($id, $userId) { $info = $this->model->with(['member'])->where(['id'=> $id,'mark'=>1])->first(); if($info && isset($info['member'])){ if(isset($info['member']['avatar'])){ $info['member']['avatar'] = $info['member']['avatar']? $info['member']['avatar'] : '/images/member/logo.png'; $info['member']['avatar'] = get_image_url($info['member']['avatar']); } if($info['user_id'] == $userId){ $info['is_follow'] = 1; }else{ $checkFollow = MemberCollectService::make()->checkCollect($userId, $info['user_id'], 1); $info['is_follow'] = $checkFollow? 1 : 0; } // 观看权限 $info['view_limit'] = 0; // 浏览历史 if(!VideoCollectService::make()->getCollectCacheInfo($userId, $id, 1)){ $data = [ 'user_id'=> $userId, 'type'=> 1, 'collect_id'=> $id, 'collect_uid'=> isset($info['user_id'])? $info['user_id'] : 0, 'tags'=> isset($info['tags'])? $info['tags'] : '', 'create_time'=> time(), 'status'=> 1, ]; VideoCollectModel::insert($data); RedisService::clear("caches:videos:recommend:{$userId}_2"); RedisService::clear("caches:member:fans:{$userId}_{$id}_2"); } // 更新播放量 if(!RedisService::get("caches:live:player:{$userId}_{$id}")){ $this->model->where(['id'=> $id])->update(['views'=>DB::raw('views + 1'),'update_time'=>time()]); RedisService::set("caches:live:player:{$userId}_{$id}", ['user_id'=> $userId,'id'=>$id], rand(600, 1800)); $info['views'] += 1; } // 结束直播 /*if($info['create_time'] <= time() - 2 * 86400 && $info['status'] == 1){ $this->model->where(['id'=> $id])->update(['status'=>2,'update_time'=>time()]); $info['status'] = 2; }*/ } return $info; } /** * 更新播放浏览历史 * @param $userId 用户ID * @param $id 视频ID * @return false */ public function updatePlay($userId, $id) { // 浏览历史 if(!VideoCollectService::make()->getCollectCacheInfo($userId, $id, 1,2)){ $info = $this->model->from('live as a') ->where(['a.id'=> $id,'a.mark'=>1]) ->select(['a.id','a.category','a.user_id']) ->first(); if(empty($info)){ return false; } $data = [ 'user_id'=> $userId, 'type'=> 1, 'source_type'=> 2, 'collect_id'=> $id, 'category_id'=> isset($info['category'])? $info['category'] : 0, 'collect_uid'=> isset($info['user_id'])? $info['user_id'] : 0, 'create_time'=> time(), 'status'=> 1, ]; VideoCollectModel::insert($data); RedisService::set("caches:videos:collect:temp_{$userId}_{$id}_1_2", $data, rand(10,30)); RedisService::clear("caches:videos:recommend:{$userId}_1_2"); RedisService::clear("caches:member:fans:{$userId}_{$id}_2"); } // 更新播放量 if(!RedisService::get("caches:player:live:{$userId}_{$id}")){ $this->model->where(['id'=> $id])->update(['views'=>DB::raw('views + 1'),'update_time'=>time()]); RedisService::set("caches:player:live:{$userId}_{$id}", ['user_id'=> $userId,'id'=>$id], rand(6*3600, 86400)); } $this->error = 1010; return true; } /** * 缓存数据 * @param $id * @param int $status * @return array|mixed */ public function getCacheInfo($id, $status=0) { $cacheKey = "caches:live:info:{$id}_{$status}"; $info = RedisService::get($cacheKey); if($info){ return $info; } $where = ['id' => $id, 'mark' => 1]; if($status){ $where['status'] = $status; } $info = $this->model->where($where) ->select(['id','user_id','create_time','end_time','views','reward_num','reward_total','like_num']) ->first(); $info = $info? $info->toArray() : []; if($info){ RedisService::set($cacheKey, $info, rand(3, 5)); } return $info; } /** * 状态设置 * @return bool */ public function status() { $id = request()->post('id', 0); $status = request()->post('status', 2); if ($id && !$info = $this->getCacheInfo($id)) { $this->error = 2981; return false; } $updateData = ['status'=>$status, 'update_time'=> time()]; if($status == 2){ $updateData['end_time'] = time(); } if($this->model->where(['id'=> $id,'mark'=>1])->update($updateData)){ $this->error = 1002; $time = $info['end_time']? intval($info['end_time'] - $info['create_time']) : intval(time() - $info['create_time']); $info['status'] = $status; $info['live_hour'] = $time > 3600 ? intval($time/3600) : 0; $info['live_minute'] = $time%3600? intval($time%3600/60) : 0; $info['time_text'] = date('H:i', $info['create_time']); $info['end_time_text'] = date('H:i', $info['end_time']? $info['end_time'] : time()); $info['fans_num'] = MemberCollectService::make()->getViewFansCountByType($info['user_id'], $id, 2); $info['new_fans'] = MemberCollectService::make()->getNewFansCount($info['user_id'], $id,2, $info['create_time']); return $info; } $this->error = 1003; return true; } /** * 获取直播推流/拉流地址 * @param $streamName * @param string $appName * @param string $playType * @param int $expireTime * @return array */ public function getLiveUrl($streamName, $appName = 'xlapp', $playType = 'rtmp', $expireTime = 1440) { $cachekey = "caches:live:urls:{$streamName}_{$appName}_{$playType}"; $datas = RedisService::get($cachekey); if($datas){ return $datas; } $playUrls = []; //未开启鉴权Key的情况下 $pushDomain = ConfigService::make()->getConfigByCode('live_push_url'); $playDomain = ConfigService::make()->getConfigByCode('live_play_url'); $pushKey = ConfigService::make()->getConfigByCode('push_url_key'); $playKey = ConfigService::make()->getConfigByCode('play_url_key'); $timeStamp = time() + $expireTime * 60; if ($pushKey == '') { $pushUrl = 'rtmp://' . $pushDomain . '/' . $appName . '/' . $streamName; } else { $sstring = '/' . $appName . '/' . $streamName . '-' . $timeStamp . '-0-0-' . $pushKey; $md5hash = md5($sstring); $pushUrl = 'rtmp://' . $pushDomain . '/' . $appName . '/' . $streamName . '?auth_key=' . $timeStamp . '-0-0-' . $md5hash; } if ($playKey == '') { $playUrls['rtmp'] = 'rtmp://' . $playDomain . '/' . $appName . '/' . $streamName; $playUrls['flv'] = 'http://' . $playDomain . '/' . $appName . '/' . $streamName . '.flv'; $playUrls['hls'] = 'http://' . $playDomain . '/' . $appName . '/' . $streamName . '.m3u8'; } else { $rtmpSstring = '/' . $appName . '/' . $streamName . '-' . $timeStamp . '-0-0-' . $playKey; $rtmpMd5hash = md5($rtmpSstring); $playUrls['rtmp'] = 'rtmp://' . $playDomain . '/' . $appName . '/' . $streamName . '?auth_key=' . $timeStamp . '-0-0-' . $rtmpMd5hash; $flvSstring = '/' . $appName . '/' . $streamName . '.flv-' . $timeStamp . '-0-0-' . $playKey; $flvMd5hash = md5($flvSstring); $playUrls['flv'] = 'http://' . $playDomain . '/' . $appName . '/' . $streamName . '.flv?auth_key=' . $timeStamp . '-0-0-' . $flvMd5hash; $hlsSstring = '/' . $appName . '/' . $streamName . '.m3u8-' . $timeStamp . '-0-0-' . $playKey; $hlsMd5hash = md5($hlsSstring); $playUrls['hls'] = 'http://' . $playDomain . '/' . $appName . '/' . $streamName . '.m3u8?auth_key=' . $timeStamp . '-0-0-' . $hlsMd5hash; } $datas = [ 'push_url' => $pushUrl, 'play_url' => isset($playUrls[$playType]) ? $playUrls[$playType] : '', 'play_urls' => $playUrls, ]; RedisService::set($cachekey, $datas, 1800); return $datas; } /** * 创建直播间 * @param $userId 直逼用户 * @param $params * @return array|false */ public function create($userId, $params) { $liveLevel = ConfigService::make()->getConfigByCode('live_open_level'); $liveLevel = $liveLevel > 0 ? $liveLevel : 0; $userInfo = MemberModel::where(['id' => $userId, 'mark' => 1])->select(['id', 'nickname', 'member_level', 'status'])->first(); $status = isset($userInfo['status']) ? $userInfo['status'] : 0; $nickname = isset($userInfo['nickname']) ? $userInfo['nickname'] : ''; $memberLevel = isset($userInfo['member_level']) ? $userInfo['member_level'] : 0; if (empty($userInfo) || $status != 1) { $this->error = 2024; return false; } if($memberLevel < $liveLevel){ $this->error = 2040; return false; } // 验证是否有开播中断播的继续播 $data = [ 'type' => isset($params['type']) ? intval($params['type']) : 1, 'user_id' => $userId, 'title' => isset($params['title']) && $params['title'] ? trim($params['title']) : $nickname.'正在直播', 'description' => isset($params['description']) && $params['description'] ? trim($params['description']) : lang('我正在直播,快来看看吧'), 'category' => isset($params['category']) ? intval($params['category']) : 0, 'visible_type' => isset($params['visible_type']) ? intval($params['visible_type']) : 0, 'visible_users' => isset($params['visible_users']) ? trim($params['visible_users']) : '', 'chat_type' => isset($params['chat_type']) ? intval($params['chat_type']) : 0, 'chat_status' => isset($params['chat_status']) ? intval($params['chat_status']) : 1, 'pay_status' => isset($params['pay_status']) ? intval($params['pay_status']) : 1, 'open_area' => isset($params['open_area']) ? intval($params['open_area']) : 1, 'view_allow' => isset($params['view_allow']) ? intval($params['view_allow']) : 1, 'push_url' => isset($params['push_url']) ? trim($params['push_url']) : '', 'play_url' => isset($params['play_url']) ? trim($params['play_url']) : '', 'update_time'=> time(), 'status' =>1, 'mark'=>1, ]; if($liveId = $this->model->where(['user_id'=> $userId,'status'=>1,'mark'=>1])->orderBy('create_time','desc')->value('id')){ if(!$this->model->where(['user_id'=> $userId,'status'=>1,'mark'=>1])->update($data)){ $this->error = 2042; return false; } }else{ $data['create_time'] = time(); $this->model->where(['user_id'=> $userId,'mark'=>1])->update(['status'=>2,'update_time'=>time()]); if(!$liveId = $this->model->insertGetId($data)){ $this->error = 2042; return false; } } $data['id'] = $liveId; $data['member'] = MemberModel::where(['id'=> $userId])->select(['id','nickname','avatar','status'])->first(); $data['member'] = $data['member']? $data['member'] : []; $data['member']['is_follow'] = 1; if(isset($data['member']['avatar'])){ $data['member']['avatar'] = $data['member']['avatar']? $data['member']['avatar'] : '/images/member/logo.png'; $data['member']['avatar'] = get_image_url($data['member']['avatar']); } $this->error = 2041; return $data; } }