M3u8Controller.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. namespace Modules\Api\Controllers;
  3. use App\Constants\CommonConstant;
  4. use App\Constants\VideoConstant;
  5. use App\Model\UserModel;
  6. use App\Repositories\AdRepository;
  7. use App\Repositories\OriginalRepository;
  8. use App\Repositories\Service\CommonService;
  9. use App\Repositories\ShopsRepository;
  10. use App\Repositories\UserRepository;
  11. use App\Repositories\Utils\JwtRepository;
  12. use App\Repositories\Utils\M3u8CacheRepository;
  13. use App\Repositories\VideoRepository;
  14. use Carbon\Carbon;
  15. use Hyperf\HttpServer\Request;
  16. use Modules\Api\Services\AuthService;
  17. use Psr\SimpleCache\CacheInterface;
  18. class M3u8Controller extends BaseController
  19. {
  20. /**
  21. * 获取raw
  22. * @param Request $request
  23. * @param VideoRepository $videoRepository
  24. * @param AdRepository $adRepository
  25. * @return \Psr\Http\Message\ResponseInterface
  26. */
  27. public function raw(Request $request, VideoRepository $videoRepository, AdRepository $adRepository, OriginalRepository $goodsRepository, ShopsRepository $shopsRepository)
  28. {
  29. $zuhe = urldecode($request->input('tk', ''));
  30. $module = trim(strtolower($request->input('module', '')));
  31. $pk = substr($request->input('pk', ''), 0, 16);
  32. $inputSign = $request->input('sn', '');
  33. $timestamp = intval($request->input('tm', 0));
  34. $rateType = substr($request->input('rate_type', ''), 0, 32); // 指定的比特率.
  35. $authService = new AuthService();
  36. if (empty($zuhe)) {
  37. return self::responseM3u8Raw('参数错误1', true);
  38. }
  39. if (!in_array($module, ['video', 'ad', 'goods', 'shop'])) {
  40. // 判断模块合法性.
  41. return self::responseM3u8Raw('参数错误2', true);
  42. }
  43. $nowDateObj = Carbon::now();
  44. $startCheckDate = '2022-08-01 04:45:33';
  45. $startCheckObj = Carbon::parse($startCheckDate);
  46. if ($nowDateObj > $startCheckObj && in_array($module, ['video'])) {
  47. // 大于这个日期, 且视频模块, 才检查签名参数.
  48. if (!empty($timestamp) && strlen($timestamp) == 10) {
  49. $inputDateObj = Carbon::createFromTimestamp($timestamp);
  50. if (abs($inputDateObj->diffInHours($nowDateObj)) >= 3) {
  51. return self::responseM3u8Raw('参数错误6', true);
  52. }
  53. } else {
  54. return self::responseM3u8Raw('参数错误7', true);
  55. }
  56. }
  57. $zuhe2 = caesar_decode($zuhe, 'l,n', 3); // 偏移了3
  58. $previewFlag = substr($zuhe2, 0, 2);
  59. $queryDeviceType = substr($zuhe2, 2, 1);
  60. $queryDeviceNo = substr($zuhe2, 7, strlen($zuhe2) - 7);
  61. $sign = M3u8CacheRepository::generateSign($zuhe2, $timestamp);
  62. if ($sign !== $inputSign) {
  63. return self::responseM3u8Raw('参数错误8', true);
  64. }
  65. $cacheKey = $authService->getUserDeviceCacheKey($queryDeviceNo, $queryDeviceType);
  66. $cache = make(CacheInterface::class);
  67. $cacheToken = $cache->has($cacheKey) ? $cache->get($cacheKey) : '';
  68. if (empty($cacheToken)) {
  69. return self::responseM3u8Raw('参数错误4', true);
  70. }
  71. $jwtRepo = make(JwtRepository::class);
  72. $aud = 'app-users';
  73. $validateResult = $jwtRepo->validateToken($cacheToken, $aud);
  74. if (!$validateResult['is_valid']) {
  75. if ($validateResult['is_expired']) {
  76. return self::responseM3u8Raw('参数错误2', true);
  77. }
  78. return self::responseM3u8Raw('参数错误3', true);
  79. }
  80. // 破解版专门一个m3u8
  81. $tokenObj = $validateResult['token_obj'];
  82. $hUserCode = $tokenObj->claims()->get('user_id');
  83. // $hDeviceNo = $tokenObj->claims()->get('device_no');
  84. $hDeviceType = $tokenObj->claims()->get('device_type');
  85. // $hFingerHash = $tokenObj->claims()->get('finger_hash');
  86. $hIp = $tokenObj->claims()->get('ip');
  87. $hVersion = $tokenObj->claims()->get('version');
  88. if ($hDeviceType == 'A') {
  89. if (version_compare($hVersion, '1.1.1', '<') || version_compare($hVersion, '1.99.1', '>')) {
  90. $crackedM3u8Url = 'v2|/20220831/pPlRwPcF/1000kb/hls/index.m3u8';
  91. $m3u8Params1 = [
  92. 'url' => $crackedM3u8Url,
  93. 'is_preview' => false,
  94. ];
  95. $raw = M3u8CacheRepository::getMu38Raw($m3u8Params1, true);
  96. return self::responseM3u8Raw($raw);
  97. }
  98. }
  99. // M3u8验证IP.
  100. $clientIp = make(CommonService::class)->getIp();
  101. if ($hIp !== $clientIp) {
  102. // make(UserRepository::class)->userLogout($hUserCode);
  103. logger('api-M3u8Controller-ip', ['token_ip' => $hIp, 'user_ip' => $clientIp]);
  104. // return self::responseM3u8Raw('参数错误9', true);
  105. }
  106. $cache = make(CacheInterface::class);
  107. $rateTypeKey = !empty($rateType) ? $rateType : 'empty';
  108. $cacheKey = $module . ':raw:'. $pk . ':'. $rateTypeKey;
  109. $cacheSeconds = 7 * 24 * 60 * 60;
  110. $arrItem = $cache->get($cacheKey, []);
  111. $m3u8PlayUrl = '';
  112. $previewTime = VideoConstant::PREVIEW_TIME;
  113. $playerType = 1;
  114. if (empty($arrItem)) {
  115. switch ($module) {
  116. case 'video':
  117. $item = $videoRepository->findBy('id', $pk, ['id', 'mu']);
  118. if (!empty($item->mu)) {
  119. $m3u8PlayUrl = $item->mu;
  120. }
  121. break;
  122. case 'ad':
  123. $item = $adRepository->findBy('id', $pk, ['id', 'play']);
  124. if (!empty($item->play)) {
  125. $m3u8PlayUrl = $item->play;
  126. }
  127. break;
  128. case 'shop':
  129. $item = $shopsRepository->findBy('uid', $pk, ['video_address']);
  130. if (!empty($item->video_address)) {
  131. $m3u8PlayUrl = $item->video_address;
  132. }
  133. break;
  134. case 'goods':
  135. $item = $goodsRepository->findBy('uid', $pk, ['video_address']);
  136. if (!empty($item->video_address)) {
  137. $m3u8PlayUrl = $item->video_address;
  138. }
  139. break;
  140. }
  141. if (empty($item)) {
  142. return self::responseM3u8Raw('参数错误5', true);
  143. }
  144. $arrItem = $item->toArray();
  145. $arrItem['m3u8_play_url'] = $m3u8PlayUrl;
  146. $cache->set($cacheKey, $arrItem, $cacheSeconds);
  147. }
  148. $isPreview = true; // 默认预览
  149. if ($previewFlag == 'k4') { // 标识 k4=完整,t5=预览
  150. $isPreview = false;
  151. }
  152. $m3u8FieldUrl = $arrItem['m3u8_play_url'];
  153. $itemInfo = [
  154. 'url' => $m3u8FieldUrl,
  155. 'is_preview' => $isPreview,
  156. // 'preview_time' => $previewTime,
  157. ];
  158. $raw = M3u8CacheRepository::getMu38Raw($itemInfo, true);
  159. return self::responseM3u8Raw($raw);
  160. }
  161. /**
  162. * @return string[]
  163. */
  164. private function getHeaders()
  165. {
  166. return [
  167. 'Content-Type' => 'application/x-mpegURL',
  168. ];
  169. }
  170. /**
  171. * 返回raw
  172. * @param $raw
  173. * @param bool $isError
  174. * @return \Psr\Http\Message\ResponseInterface
  175. */
  176. private function responseM3u8Raw($raw, $isError = false)
  177. {
  178. if (config('app_debug') && $isError == true && config('app_env') != 'prod') {
  179. return json_fail($raw);
  180. } else {
  181. if ($isError == true) {
  182. return response('', 200, self::getHeaders());
  183. } else {
  184. return response($raw, 200, self::getHeaders());
  185. }
  186. }
  187. }
  188. }