KVSController.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. # -*- coding: utf-8 -*-
  2. """
  3. @Author : Rocky
  4. @Time : 2022/10/18 9:48
  5. @File :KVSController.py
  6. """
  7. import base64
  8. import hashlib
  9. import json
  10. import time
  11. import uuid
  12. import datetime
  13. import boto3
  14. import botocore
  15. import requests
  16. from django.http import HttpResponse
  17. from django.views import View
  18. from Model.models import KVS, Device_User, Device_Info
  19. from Object.AWS.AmazonKinesisVideoUtil import AmazonKinesisVideoObject, AmazonKVAMObject
  20. from Object.IOTCore.IotObject import IOTClient
  21. from Object.RedisObject import RedisObject
  22. from Object.ResponseObject import ResponseObject
  23. from Ansjer.config import ACCESS_KEY_ID, SECRET_ACCESS_KEY, SERVER_DOMAIN, LOGGER, KVS_REGION, REGION_ID_LIST
  24. from Object.TokenObject import TokenObject
  25. from botocore.auth import SigV4Auth
  26. from botocore.awsrequest import AWSRequest
  27. from botocore.credentials import Credentials
  28. from Service.CommonService import CommonService
  29. class UserRelatedView(View):
  30. def get(self, request, *args, **kwargs):
  31. request.encoding = 'utf-8'
  32. operation = kwargs.get('operation')
  33. return self.validation(request.GET, operation, request)
  34. def post(self, request, *args, **kwargs):
  35. request.encoding = 'utf-8'
  36. operation = kwargs.get('operation')
  37. return self.validation(request.POST, operation, request)
  38. def validation(self, request_dict, operation, request):
  39. response = ResponseObject()
  40. if operation == 'generate-qr-code': # 网页生成二维码
  41. return self.generate_qr_code(response)
  42. elif operation == 'get-scanning-status': # 确认app是否扫码
  43. return self.get_scanning_status(request_dict, response)
  44. elif operation == 'web-login': # 网页登录
  45. return self.web_login(request_dict, response)
  46. elif operation == 'pc-login': # pc端登录
  47. return self.pc_login(request_dict, response)
  48. elif operation == 'confirm-login': # app确认登录
  49. return self.confirm_login(request_dict, response)
  50. else:
  51. tko = TokenObject(
  52. request.META.get('HTTP_AUTHORIZATION'),
  53. returntpye='pc')
  54. if tko.code != 0:
  55. return response.json(tko.code)
  56. response.lang = tko.lang
  57. user_id = tko.userID
  58. if operation == 'get-device': # 获取设备列表
  59. return self.get_device(response, user_id)
  60. else:
  61. return response.json(404)
  62. @staticmethod
  63. def get_user_info(user_id):
  64. """
  65. 获取用户信息
  66. @param user_id: 用户id
  67. @return: response
  68. """
  69. device_user_qs = Device_User.objects.filter(userID=user_id).values('NickName', 'userIconPath',
  70. 'userIconUrl')
  71. if not device_user_qs.exists():
  72. user_icon_url = ''
  73. nick_name = ''
  74. else:
  75. users = device_user_qs.first()
  76. nick_name = users['NickName']
  77. user_icon_path = str(users['userIconPath'])
  78. if user_icon_path:
  79. user_icon_path = user_icon_path.replace('static/', '').replace('\\', '/')
  80. user_icon_url = SERVER_DOMAIN + 'account/getAvatar/' + user_icon_path
  81. else:
  82. user_icon_url = ''
  83. return user_icon_url, nick_name
  84. @staticmethod
  85. def generate_qr_code(response):
  86. """
  87. 网页生成二维码
  88. @param response: 响应对象
  89. @return: response
  90. """
  91. nwo_time = time.time()
  92. redis_obj = RedisObject()
  93. try:
  94. uuid_number = hashlib.md5((str(uuid.uuid1()) + str(nwo_time)).encode('utf-8')).hexdigest()
  95. flag = redis_obj.set_ex_data(uuid_number, 0, 300) # redis记录uuid,状态为生成二维码
  96. res = {'type': 'autologin', 'id': uuid_number}
  97. if flag:
  98. return response.json(0, res)
  99. else:
  100. return response.json(119)
  101. except Exception as e:
  102. print(e)
  103. return response.json(500)
  104. @staticmethod
  105. def get_scanning_status(request_dict, response):
  106. """
  107. 获取app扫码状态
  108. @param request_dict: 请求参数
  109. @request_dict serial_number: 序列号
  110. @param response: 响应对象
  111. @return: response
  112. """
  113. uuid_number = request_dict.get('uuid', None)
  114. if not uuid_number:
  115. return response.json(444, {'error param': 'uuid'})
  116. try:
  117. redis_obj = RedisObject()
  118. status = redis_obj.get_data(uuid_number)
  119. if status is False:
  120. return response.json(119)
  121. elif status == '1' or status == '2':
  122. res = {'status': 1} # 已扫码
  123. else:
  124. res = {'status': 0} # 未扫码
  125. return response.json(0, res)
  126. except Exception as e:
  127. print(e)
  128. return response.json(500)
  129. @staticmethod
  130. def pc_login(request_dict, response):
  131. """
  132. pc端登录
  133. @param request_dict: 请求参数
  134. @request_dict serial_number: 序列号
  135. @param response: 响应对象
  136. @return: response
  137. """
  138. uuid_number = request_dict.get('uuid', None)
  139. if not uuid_number:
  140. return response.json(444, {'error param': 'uuid'})
  141. try:
  142. redis_obj = RedisObject()
  143. status = redis_obj.get_data(uuid_number)
  144. token = redis_obj.get_data(uuid_number + 'token')
  145. if status is False or token is False:
  146. return response.json(119)
  147. elif status == '2': # 已登录
  148. token_obj = TokenObject(token)
  149. response.lang = token_obj.lang
  150. if token_obj.code != 0:
  151. return response.json(token_obj.code)
  152. user_id = token_obj.userID
  153. user_icon_url, nick_name = UserRelatedView.get_user_info(user_id)
  154. res = {'status': 1, 'userIconUrl': user_icon_url, 'nickName': nick_name, 'token': token}
  155. redis_obj.del_data(uuid_number)
  156. redis_obj.del_data(uuid_number + 'token')
  157. else: # 未登录
  158. res = {'status': 0}
  159. return response.json(0, res)
  160. except Exception as e:
  161. print(e)
  162. return response.json(500)
  163. @staticmethod
  164. def web_login(request_dict, response):
  165. """
  166. 网页登录
  167. @param request_dict: 请求参数
  168. @request_dict serial_number: 序列号
  169. @param response: 响应对象
  170. @return: response
  171. """
  172. uuid_number = request_dict.get('uuid', None)
  173. confirm = request_dict.get('confirm', None)
  174. if not all([uuid_number, confirm]):
  175. return response.json(444, {'error param': 'uuid or confirm'})
  176. try:
  177. redis_obj = RedisObject()
  178. if confirm == '1': # 取消登录
  179. redis_obj.del_data(uuid_number)
  180. redis_obj.del_data(uuid_number + 'token')
  181. return response.json(0)
  182. token = redis_obj.get_data(uuid_number + 'token')
  183. ttl = redis_obj.get_ttl(uuid_number)
  184. if token is False or ttl <= 0:
  185. return response.json(119)
  186. result = redis_obj.set_ex_data(uuid_number, 2, ttl) # 修改uuid状态为已登录
  187. if result is False:
  188. return response.json(119)
  189. return response.json(0)
  190. except Exception as e:
  191. print(e)
  192. return response.json(500)
  193. @staticmethod
  194. def confirm_login(request_dict, response):
  195. """
  196. app确认登录
  197. @param request_dict: 请求参数
  198. @request_dict serial_number: 序列号
  199. @param response: 响应对象
  200. @return: response
  201. """
  202. uuid_number = request_dict.get('uuid', None)
  203. token = request_dict.get('token', None)
  204. if not all([uuid_number, token]):
  205. return response.json(444, {'error param': 'uuid or token'})
  206. redis_obj = RedisObject()
  207. try:
  208. status = redis_obj.get_data(uuid_number)
  209. ttl = redis_obj.get_ttl(uuid_number)
  210. if status is False or ttl <= 0:
  211. return response.json(119)
  212. result1 = redis_obj.set_ex_data(uuid_number, 1, ttl) # 修改uuid状态为已扫码
  213. result2 = redis_obj.set_ex_data(uuid_number + 'token', token, ttl)
  214. if result1 is False or result2 is False:
  215. return response.json(119)
  216. return response.json(0)
  217. except Exception as e:
  218. print(e)
  219. return response.json(500)
  220. @staticmethod
  221. def get_device(response, user_id):
  222. """
  223. 获取设备列表
  224. @param response: 响应对象
  225. @param user_id: 用户id
  226. @return: response
  227. """
  228. try:
  229. device_qs = Device_Info.objects.filter(userID=user_id).values('serial_number', 'NickName')
  230. return response.json(0, list(device_qs))
  231. except Exception as e:
  232. print(e)
  233. return response.json(500)
  234. class KVSView(View):
  235. def get(self, request, *args, **kwargs):
  236. request.encoding = 'utf-8'
  237. operation = kwargs.get('operation')
  238. return self.validation(request.GET, request, operation)
  239. def post(self, request, *args, **kwargs):
  240. request.encoding = 'utf-8'
  241. operation = kwargs.get('operation')
  242. return self.validation(request.POST, request, operation)
  243. def validation(self, request_dict, request, operation):
  244. response = ResponseObject()
  245. if operation == 'create-media': # 创建视频流
  246. return self.create_media(request_dict, response)
  247. elif operation == 'update-data-retention': # 修改视频流数据保留时间
  248. return self.update_data_retention(request_dict, response)
  249. elif operation == 'get-sts-token': # 获取临时token
  250. return self.get_sts_token(request_dict, response)
  251. elif operation == 'createSignalChannel': # 创建通道
  252. return self.create_signal_channel(request_dict, response)
  253. elif operation == 'SendAlexaOfferToMaster': # 发送Alexa offer
  254. return self.send_alexa_offer_to_master(request_dict, response)
  255. elif operation == 'deleteSignalChannel': # 删除通道
  256. return self.delete_signal_channel(request_dict, response)
  257. else:
  258. # tko = TokenObject(request.META.get('HTTP_AUTHORIZATION'))
  259. # if tko.code != 0:
  260. # return response.json(tko.code)
  261. # response.lang = tko.lang
  262. # user_id = tko.userID
  263. if operation == 'get-device-midea-list': # 获取设备列表
  264. return self.get_device_midea_list(request_dict, response)
  265. elif operation == 'get-hls-midea': # 获取视频播放地址
  266. return self.get_hls_midea_url(request_dict, response)
  267. elif operation == 'download-clip': # 获取视频播放地址
  268. return self.download_clip(request_dict, response)
  269. else:
  270. return response.json(404)
  271. @staticmethod
  272. def create_media(request_dict, response):
  273. """
  274. 创建视频流
  275. @param request_dict: 请求参数
  276. @request_dict serial_number: 序列号
  277. @param response: 响应对象
  278. @return: response
  279. """
  280. serial_number = request_dict.get('serial_number', None)
  281. try:
  282. kvs_qs = KVS.objects.filter(stream_name=serial_number)
  283. if kvs_qs.exists():
  284. return response.json(174)
  285. kinesis_video_obj = AmazonKinesisVideoObject(
  286. aws_access_key_id=ACCESS_KEY_ID,
  287. secret_access_key=SECRET_ACCESS_KEY,
  288. region_name=KVS_REGION
  289. )
  290. stream_arn = kinesis_video_obj.create_stream(stream_name=serial_number)
  291. if stream_arn:
  292. now_time = int(time.time())
  293. KVS.objects.create(stream_name=serial_number, stream_arn=stream_arn, created_time=now_time,
  294. updated_time=now_time)
  295. return response.json(0)
  296. else:
  297. return response.json(178)
  298. except Exception as e:
  299. print(e)
  300. return response.json(500)
  301. @staticmethod
  302. def update_data_retention(request_dict, response):
  303. """
  304. 修改视频流数据保留时间
  305. @param request_dict: 请求参数
  306. @request_dict serial_number: 序列号
  307. @request_dict operation: 操作,增加/减少
  308. @request_dict data_retention_change_in_hours: 修改的时间
  309. @param response: 响应对象
  310. @return: response
  311. """
  312. serial_number = request_dict.get('serial_number', None)
  313. operation = request_dict.get('operation', None)
  314. data_retention_change_in_hours = request_dict.get('data_retention_change_in_hours', None)
  315. try:
  316. kvs_qs = KVS.objects.filter(stream_name=serial_number)
  317. if not kvs_qs.exists():
  318. return response.json(173)
  319. kinesis_video_obj = AmazonKinesisVideoObject(
  320. aws_access_key_id=ACCESS_KEY_ID,
  321. secret_access_key=SECRET_ACCESS_KEY,
  322. region_name=KVS_REGION
  323. )
  324. now_time = int(time.time())
  325. data_retention_change_in_hours = int(data_retention_change_in_hours)
  326. kinesis_video_obj.update_data_retention(stream_name=serial_number, operation=operation,
  327. data_retention_change_in_hours=data_retention_change_in_hours)
  328. kvs_qs.update(data_retention_in_hours=data_retention_change_in_hours, updated_time=now_time)
  329. return response.json(0)
  330. except Exception as e:
  331. print(e)
  332. return response.json(500)
  333. @staticmethod
  334. def get_hls_midea_url(request_dict, response):
  335. """
  336. 获取视频播放地址
  337. @param request_dict: 请求参数
  338. @request_dict serial_number: 序列号
  339. @request_dict startTime: 开始时间
  340. @request_dict endTime: 结束时间
  341. @request_dict playMode: 播放模式
  342. @param response: 响应对象
  343. @return: response
  344. """
  345. serial_number = request_dict.get('serial_number', None)
  346. start_time = request_dict.get('startTime', None)
  347. end_time = request_dict.get('endTime', None)
  348. play_mode = request_dict.get('playMode', None)
  349. if not all([serial_number, start_time, end_time, play_mode]):
  350. return response.json(444)
  351. start_time = datetime.datetime.fromtimestamp(int(start_time)) - datetime.timedelta(hours=8)
  352. end_time = datetime.datetime.fromtimestamp(int(end_time)) - datetime.timedelta(hours=8)
  353. play_mode = int(play_mode)
  354. play_mode = 'ON_DEMAND' if play_mode == 0 else 'LIVE_REPLAY'
  355. try:
  356. # kvs_qs = KVS.objects.filter(stream_name=serial_number)
  357. # if not kvs_qs.exists():
  358. # return response.json(174)
  359. kinesis_video_obj = AmazonKVAMObject(
  360. aws_access_key_id='AKIA2E67UIMD45Y3HL53',
  361. secret_access_key='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',
  362. region_name='us-east-1',
  363. stream_name=serial_number,
  364. api_name='GET_HLS_STREAMING_SESSION_URL'
  365. )
  366. hls_streaming_session_url = kinesis_video_obj.get_hls_streaming_session_url(serial_number, start_time,
  367. end_time, play_mode)
  368. return response.json(0, {"HlsStreamingSessionUrl": hls_streaming_session_url})
  369. except Exception as e:
  370. print(e)
  371. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  372. @staticmethod
  373. def get_device_midea_list(request_dict, response):
  374. """
  375. 获取视频播放列表
  376. @param request_dict: 请求参数
  377. @request_dict serial_number: 序列号
  378. @request_dict startTime: 开始时间
  379. @request_dict endTime: 结束时间
  380. @param response: 响应对象
  381. @return: response
  382. """
  383. serial_number = request_dict.get('serial_number', None)
  384. start_time = request_dict.get('startTime', None)
  385. end_time = request_dict.get('endTime', None)
  386. page = request_dict.get('page', None)
  387. size = request_dict.get('size', None)
  388. if not all([serial_number, start_time, end_time, page, size]):
  389. return response.json(444)
  390. page = int(page)
  391. size = int(size)
  392. start_time = datetime.datetime.fromtimestamp(int(start_time)) - datetime.timedelta(hours=8)
  393. end_time = datetime.datetime.fromtimestamp(int(end_time)) - datetime.timedelta(hours=8)
  394. try:
  395. # kvs_qs = KVS.objects.filter(stream_name=serial_number)
  396. # if not kvs_qs.exists():
  397. # return response.json(174)
  398. kinesis_fragments_obj = AmazonKVAMObject(
  399. aws_access_key_id='AKIA2E67UIMD45Y3HL53',
  400. secret_access_key='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',
  401. region_name='us-east-1',
  402. stream_name=serial_number,
  403. api_name='LIST_FRAGMENTS'
  404. )
  405. kinesis_images_obj = AmazonKVAMObject(
  406. aws_access_key_id='AKIA2E67UIMD45Y3HL53',
  407. secret_access_key='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',
  408. region_name='us-east-1',
  409. stream_name=serial_number,
  410. api_name='GET_IMAGES'
  411. )
  412. stream_list = kinesis_fragments_obj.get_list_fragments(serial_number, start_time, end_time)
  413. total_page = len(stream_list)
  414. stream_list = stream_list[(page - 1) * size:page * size]
  415. for item in stream_list:
  416. temp_start_time = (item['startTime'] - datetime.timedelta(hours=8)).replace(
  417. tzinfo=datetime.timezone.utc)
  418. temp_end_time = temp_start_time + datetime.timedelta(seconds=300)
  419. item['image'] = kinesis_images_obj.get_images(serial_number, temp_start_time, temp_end_time)
  420. item['startTime'] = int(item['startTime'].timestamp())
  421. item['endTime'] = int(item['endTime'].timestamp())
  422. res = {
  423. 'totalPage': total_page,
  424. 'fragments': stream_list
  425. }
  426. return response.json(0, res)
  427. except Exception as e:
  428. print(e)
  429. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  430. @staticmethod
  431. def download_clip(request_dict, response):
  432. """
  433. 获取视频播放地址
  434. @param request_dict: 请求参数
  435. @request_dict serial_number: 序列号
  436. @request_dict startTime: 开始时间
  437. @request_dict endTime: 结束时间
  438. @param response: 响应对象
  439. @return: response
  440. """
  441. serial_number = request_dict.get('serial_number', None)
  442. start_time = request_dict.get('startTime', None)
  443. end_time = request_dict.get('endTime', None)
  444. if not all([serial_number, start_time, end_time]):
  445. return response.json(444)
  446. start_time = datetime.datetime.fromtimestamp(int(start_time)) - datetime.timedelta(hours=8)
  447. end_time = datetime.datetime.fromtimestamp(int(end_time)) - datetime.timedelta(hours=8)
  448. try:
  449. # kvs_qs = KVS.objects.filter(stream_name=serial_number)
  450. # if not kvs_qs.exists():
  451. # return response.json(174)
  452. kinesis_video_obj = AmazonKVAMObject(
  453. aws_access_key_id='AKIA2E67UIMD45Y3HL53',
  454. secret_access_key='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',
  455. region_name='us-east-1',
  456. stream_name=serial_number,
  457. api_name='GET_CLIP'
  458. )
  459. clip_obj, clip_size = kinesis_video_obj.get_clip(serial_number, start_time, end_time)
  460. res = HttpResponse(clip_obj.read())
  461. res["content_type"] = "video/mp4"
  462. res["Content-Disposition"] = "attachment;filename=video.mp4"
  463. res['Content-Length'] = str(clip_size)
  464. return res
  465. except Exception as e:
  466. print(e)
  467. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  468. @staticmethod
  469. def get_sts_token(request_dict, response):
  470. """
  471. 获取临时token
  472. @param request_dict: 请求参数
  473. @request_dict uid: 设备uid
  474. @param response: 响应对象
  475. @return: response
  476. """
  477. uid = request_dict.get('uid', None)
  478. # if not all([]):
  479. # return response.json(444)
  480. try:
  481. sts_client_conn = boto3.client(
  482. 'sts',
  483. aws_access_key_id=ACCESS_KEY_ID,
  484. aws_secret_access_key=SECRET_ACCESS_KEY,
  485. region_name=KVS_REGION
  486. )
  487. sts_obj = sts_client_conn.get_session_token(DurationSeconds=129600)
  488. res = {
  489. 'AccessKeyId': sts_obj['Credentials']['AccessKeyId'],
  490. 'AccessKeySecret': sts_obj['Credentials']['SecretAccessKey'],
  491. 'SessionToken': sts_obj['Credentials']['SessionToken'],
  492. 'Expiration': str(sts_obj['Credentials']['Expiration'])
  493. }
  494. return response.json(0, res)
  495. except Exception as e:
  496. print(e)
  497. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  498. @staticmethod
  499. def create_signal_channel(request_dict, response):
  500. """
  501. 创建信号通道
  502. @param request_dict: 请求参数
  503. @request_dict serial: 序列号
  504. @param response: 响应对象
  505. @return: response
  506. """
  507. serial = request_dict.get('serial', None)
  508. if not all([serial]):
  509. return response.json(444)
  510. try:
  511. # 获取并判断region_id是否有效
  512. region_id = CommonService.confirm_region_id()
  513. if region_id not in REGION_ID_LIST:
  514. return response.json(444, {'invalid region_id': region_id})
  515. # 获取iot:CredentialProvider
  516. endpoint_type = 'iot:CredentialProvider'
  517. iot_client = IOTClient(region_id)
  518. iot_credential_provider_endpoint = iot_client.describe_iot_endpoint(endpoint_type)
  519. # 已有数据直接返回
  520. res = {
  521. 'region': KVS_REGION,
  522. 'role_alias': 'KvsCameraIoTRoleAlias',
  523. 'iot_credential_provider_endpoint': iot_credential_provider_endpoint,
  524. }
  525. channel_name = 'Ansjer_Device_{}'.format(serial)
  526. kvs = KVS.objects.filter(channel_name=channel_name)
  527. if kvs.exists():
  528. return response.json(0, res)
  529. kinesis_video_obj = AmazonKinesisVideoObject(
  530. aws_access_key_id=ACCESS_KEY_ID,
  531. secret_access_key=SECRET_ACCESS_KEY,
  532. region_name=KVS_REGION
  533. )
  534. channel_arn = kinesis_video_obj.create_signaling_channel(channel_name=channel_name)
  535. now_time = int(time.time())
  536. KVS.objects.create(
  537. channel_name=channel_name, channel_arn=channel_arn, channel_ttl=60,
  538. created_time=now_time, updated_time=now_time
  539. )
  540. return response.json(0, res)
  541. except Exception as e:
  542. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  543. @classmethod
  544. def send_alexa_offer_to_master(cls, request_dict, response):
  545. """
  546. 发送Alexa offer
  547. @param request_dict: 请求参数
  548. @request_dict serial: 序列号
  549. @param response: 响应对象
  550. @return: response
  551. """
  552. uid = request_dict.get('uid', None)
  553. sdp_offer = request_dict.get('sdp_offer', None)
  554. if not all([uid, sdp_offer]):
  555. return response.json(444)
  556. try:
  557. serial = CommonService.get_serial_number_by_uid(uid)
  558. channel_name = 'Ansjer_Device_{}'.format(serial)
  559. kvs_qs = KVS.objects.filter(channel_name=channel_name).values('channel_arn')
  560. if not kvs_qs.exists():
  561. return response.json(173)
  562. channel_arn = kvs_qs[0]['channel_arn']
  563. kinesis_video_obj = AmazonKinesisVideoObject(
  564. aws_access_key_id=ACCESS_KEY_ID,
  565. secret_access_key=SECRET_ACCESS_KEY,
  566. region_name=KVS_REGION
  567. )
  568. endpoint = kinesis_video_obj.get_signaling_channel_endpoint(channel_arn)
  569. url = '{}/v1/send-alexa-offer-to-master'.format(endpoint)
  570. # 构造请求 body
  571. client_id = hashlib.md5((str(uuid.uuid1()) + str(int(time.time()))).encode('utf-8')).hexdigest()
  572. # offer转base64
  573. offer = {
  574. 'type': 'offer',
  575. 'sdp': sdp_offer
  576. }
  577. offer = cls.dict_to_base64(offer)
  578. LOGGER.info('offer:{}'.format(offer))
  579. payload = {
  580. 'ChannelARN': channel_arn,
  581. 'SenderClientId': client_id,
  582. 'MessagePayload': offer
  583. }
  584. # 构造 AWSRequest 并签名
  585. req = AWSRequest(method='POST', url=url, data=json.dumps(payload))
  586. credentials = Credentials(ACCESS_KEY_ID, SECRET_ACCESS_KEY)
  587. SigV4Auth(credentials, 'kinesisvideo', KVS_REGION).add_auth(req)
  588. # 使用 requests 发送签名后的请求
  589. headers = dict(req.headers)
  590. headers['Content-Type'] = 'application/json'
  591. r = requests.post(url, headers=headers, data=json.dumps(payload))
  592. assert r.status_code == 200
  593. LOGGER.info('SendAlexaOfferToMaster响应: {}'.format(r.json()))
  594. sdp_answer = r.json()['Answer']
  595. assert sdp_answer
  596. # answer转字典
  597. sdp_answer = cls.base64_to_dict(sdp_answer)
  598. sdp_answer = sdp_answer['sdp']
  599. res = {
  600. 'sdp_answer': sdp_answer
  601. }
  602. return response.json(0, res)
  603. except Exception as e:
  604. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  605. @staticmethod
  606. def dict_to_base64(data_dict: dict) -> str:
  607. # 手动构建JSON字符串,避免自动转义
  608. json_str = '{\n "type": "%s",\n "sdp": "%s"\n}' % (
  609. data_dict["type"],
  610. data_dict["sdp"].replace('"', '\\"').replace('\r', '\\r').replace('\n', '\\n')
  611. )
  612. # 转换为bytes并进行base64编码
  613. base64_data = base64.b64encode(json_str.encode('utf-8'))
  614. return base64_data.decode('utf-8')
  615. @staticmethod
  616. def base64_to_dict(encoded_str: str) -> dict:
  617. decoded_bytes = base64.b64decode(encoded_str)
  618. return json.loads(decoded_bytes.decode('utf-8'))
  619. @staticmethod
  620. def delete_signal_channel(request_dict, response):
  621. """
  622. 删除信号通道
  623. @param request_dict: 请求参数
  624. @request_dict serial: 序列号
  625. @param response: 响应对象
  626. @return: response
  627. """
  628. serial = request_dict.get('serial', None)
  629. if not all([serial]):
  630. return response.json(444)
  631. try:
  632. channel_name = 'Ansjer_Device_{}'.format(serial)
  633. kvs = KVS.objects.filter(channel_name=channel_name).first()
  634. if not kvs:
  635. return response.json(173)
  636. channel_arn = kvs.channel_arn
  637. kinesis_video_obj = AmazonKinesisVideoObject(
  638. aws_access_key_id=ACCESS_KEY_ID,
  639. secret_access_key=SECRET_ACCESS_KEY,
  640. region_name=KVS_REGION
  641. )
  642. kinesis_video_obj.delete_signaling_channel(channel_arn=channel_arn)
  643. kvs.delete()
  644. return response.json(0)
  645. except Exception as e:
  646. return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))