使用Kinesis Video ON_DEMAND模式时遇datetime JSON序列化错误求助
嘿,这个问题我太熟了!你碰到的是AWS Lambda返回结果时的JSON序列化陷阱——list_fragments接口返回的片段数据里包含了Python的datetime对象,而JSON标准并不支持直接序列化这类对象,所以Lambda在尝试生成响应时就抛出了那个错误。
错误根源
你的代码最后返回的fragment_list是list_fragments的完整响应,其中Fragments数组里的每个片段都带有ServerTimestamp、ProducerTimestamp等datetime类型的字段。Lambda要求返回的响应必须是可JSON序列化的字典,而原生JSON无法处理Python的datetime实例,这就是报错的核心原因。
解决方案
这里提供两种实用的解决思路,你可以根据需求选择:
方案1:自定义JSON编码器(推荐,一劳永逸)
定义一个继承自json.JSONEncoder的类,专门处理datetime类型,把它转换成ISO格式的字符串。然后在返回结果时用这个编码器序列化整个响应字典。
首先添加编码器类:
class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() return super().default(obj)
然后修改返回语句,用json.dumps配合自定义编码器序列化内容:
return { 'statusCode': 200, 'body': json.dumps({ 'ResponseCode': 200, 'hls_url': hls_live_medial_url, 'fragment_list': response, }, cls=DateTimeEncoder) }
同时异常处理部分也建议统一序列化:
return { 'statusCode': 500, 'body': json.dumps({'Result': ex.response['Error']}) }
方案2:手动转换片段中的datetime字段
如果你不想修改整个响应的序列化方式,可以手动遍历Fragments数组,把每个片段里的datetime字段转换成字符串:
processed_fragments = [] for frag in fragments: processed_frag = frag.copy() # 转换所有datetime类型的时间字段 if 'ServerTimestamp' in processed_frag: processed_frag['ServerTimestamp'] = processed_frag['ServerTimestamp'].isoformat() if 'ProducerTimestamp' in processed_frag: processed_frag['ProducerTimestamp'] = processed_frag['ProducerTimestamp'].isoformat() if 'FragmentStartTime' in processed_frag: processed_frag['FragmentStartTime'] = processed_frag['FragmentStartTime'].isoformat() if 'FragmentEndTime' in processed_frag: processed_frag['FragmentEndTime'] = processed_frag['FragmentEndTime'].isoformat() processed_fragments.append(processed_frag)
然后返回时替换原有的Fragments:
return { 'ResponseCode': 200, 'hls_url': hls_live_medial_url, 'fragment_list': {**response, 'Fragments': processed_fragments}, }
完整修改后的代码(方案1)
import json import boto3, datetime from botocore.exceptions import ClientError class DateTimeEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime.datetime): return obj.isoformat() return super().default(obj) dynamodb = boto3.resource('dynamodb') def lambda_handler(event, context): try: # 获取HLS流会话端点 live_data_end_point= boto3.client('kinesisvideo', region_name ="us-east-1").get_data_endpoint( StreamName=event["device_name"], APIName='GET_HLS_STREAMING_SESSION_URL' ) live_data_end_point = live_data_end_point["DataEndpoint"] # 根据模式生成HLS URL if event["mode"]=="LIVE": media = boto3.client('kinesis-video-archived-media',region_name = 'us-east-1', endpoint_url=live_data_end_point) url_response = media.get_hls_streaming_session_url( StreamName=event["device_name"], PlaybackMode='LIVE', DiscontinuityMode='ALWAYS', Expires=1000, MaxMediaPlaylistFragmentResults=123, ) hls_url = url_response['HLSStreamingSessionURL'] hls_live_medial_url = [{'hls_url':hls_url}] else: media = boto3.client('kinesis-video-archived-media',region_name = 'us-east-1', endpoint_url=live_data_end_point) url_response = media.get_hls_streaming_session_url( StreamName=event["device_name"], PlaybackMode='ON_DEMAND', HLSFragmentSelector={ 'FragmentSelectorType':'SERVER_TIMESTAMP', 'TimestampRange': { 'StartTimestamp':datetime.datetime(2019, 5, 29, 13, 41), 'EndTimestamp': datetime.datetime(2019, 5, 29, 13, 55) } }, DiscontinuityMode='ALWAYS', Expires=1000, MaxMediaPlaylistFragmentResults=500, ) hls_url = url_response['HLSStreamingSessionURL'] hls_live_medial_url = [{'hls_url':hls_url}] # 获取片段列表 end_point_list_frag = boto3.client('kinesisvideo', region_name ="us-east-1").get_data_endpoint( StreamName=event["device_name"], APIName='LIST_FRAGMENTS' ) end_point_list_frag = end_point_list_frag["DataEndpoint"] client_frag = boto3.client('kinesis-video-archived-media', region_name = "us-east-1", endpoint_url = end_point_list_frag) response = client_frag.list_fragments( StreamName=event["device_name"], MaxResults=123, FragmentSelector={ 'FragmentSelectorType': 'SERVER_TIMESTAMP', 'TimestampRange': { 'StartTimestamp': datetime.datetime(2019, 5, 29, 13, 41), 'EndTimestamp': datetime.datetime(2019, 5, 29, 13, 55) } } ) # 使用自定义编码器序列化响应 return { 'statusCode': 200, 'body': json.dumps({ 'ResponseCode': 200, 'hls_url': hls_live_medial_url, 'fragment_list': response, }, cls=DateTimeEncoder) } except ClientError as ex: return { 'statusCode': 500, 'body': json.dumps({'Result': ex.response['Error']}) }
补充说明:你代码中手动创建的
datetime参数(比如传给Kinesis API的时间范围)是没问题的,因为AWS SDK能直接处理这类对象,只有返回给Lambda的响应里的datetime才需要序列化处理。
内容的提问来源于stack exchange,提问作者Vivek Goswami

