feat(playback): 播放票据返回各清晰度地址
This commit is contained in:
1 parent
ab58641bbe
commit
5a80e6693f
4 files changed
+68
No files matched your search
+25
@@ -5,6 +5,7 @@ import com.fruitshorts.content.dal.entity.Drama;
|
|||||||
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||||
import com.fruitshorts.content.service.DramaService;
|
import com.fruitshorts.content.service.DramaService;
|
||||||
import com.fruitshorts.content.service.EpisodeService;
|
import com.fruitshorts.content.service.EpisodeService;
|
||||||
|
import com.fruitshorts.media.dal.entity.MediaRendition;
|
||||||
import com.fruitshorts.media.service.PlaybackSigner;
|
import com.fruitshorts.media.service.PlaybackSigner;
|
||||||
import com.fruitshorts.media.service.ThumbnailUrlResolver;
|
import com.fruitshorts.media.service.ThumbnailUrlResolver;
|
||||||
import com.fruitshorts.media.service.TranscodeService;
|
import com.fruitshorts.media.service.TranscodeService;
|
||||||
@@ -147,6 +148,30 @@ class PlaybackServiceTest {
|
|||||||
verify(signer).sign("/files/episode/15/master.m3u8");
|
verify(signer).sign("/files/episode/15/master.m3u8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void should_sign_each_ready_rendition_when_episode_has_multiple_qualities() {
|
||||||
|
DramaEpisode episode = episode(16L, 1, 0, "/files/episode/origin.mp4");
|
||||||
|
episode.setHlsUrl("/files/transcoded/16/master.m3u8");
|
||||||
|
given(episodeService.getPublished(16L)).willReturn(episode);
|
||||||
|
given(signer.sign(anyString())).willAnswer(invocation -> invocation.getArgument(0) + "?s=1");
|
||||||
|
MediaRendition ready = new MediaRendition();
|
||||||
|
ready.setQuality("720p");
|
||||||
|
ready.setPlayUrl("/files/transcoded/16/720p/index.m3u8");
|
||||||
|
ready.setStatus("ready");
|
||||||
|
MediaRendition failed = new MediaRendition();
|
||||||
|
failed.setQuality("1080p");
|
||||||
|
failed.setPlayUrl("/files/transcoded/16/1080p/index.m3u8");
|
||||||
|
failed.setStatus("failed");
|
||||||
|
given(transcodeService.listRenditions(16L)).willReturn(List.of(ready, failed));
|
||||||
|
|
||||||
|
PlayRespVO resp = playbackService.play(null, 16L, "device-9", "203.0.113.8");
|
||||||
|
|
||||||
|
assertThat(resp.getTracks()).extracting(track -> track.getQuality())
|
||||||
|
.containsExactly("AUTO", "720p");
|
||||||
|
assertThat(resp.getTracks().get(1).getPlayUrl())
|
||||||
|
.isEqualTo("/files/transcoded/16/720p/index.m3u8?s=1");
|
||||||
|
}
|
||||||
|
|
||||||
private DramaEpisode episode(Long id, int isFree, int unlockCoin, String videoUrl) {
|
private DramaEpisode episode(Long id, int isFree, int unlockCoin, String videoUrl) {
|
||||||
DramaEpisode episode = new DramaEpisode();
|
DramaEpisode episode = new DramaEpisode();
|
||||||
episode.setId(id);
|
episode.setId(id);
|
||||||
|
|||||||
+25
@@ -13,6 +13,7 @@ import com.fruitshorts.media.service.TranscodeService;
|
|||||||
import com.fruitshorts.playback.dal.entity.PlaybackTicket;
|
import com.fruitshorts.playback.dal.entity.PlaybackTicket;
|
||||||
import com.fruitshorts.playback.dal.mapper.PlaybackTicketMapper;
|
import com.fruitshorts.playback.dal.mapper.PlaybackTicketMapper;
|
||||||
import com.fruitshorts.playback.vo.PlayRespVO;
|
import com.fruitshorts.playback.vo.PlayRespVO;
|
||||||
|
import com.fruitshorts.playback.vo.PlayTrackVO;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -87,6 +88,7 @@ public class PlaybackService {
|
|||||||
vo.setAccessible(true);
|
vo.setAccessible(true);
|
||||||
vo.setAccessType(entitled && !free ? ACCESS_ENTITLED : ACCESS_FREE);
|
vo.setAccessType(entitled && !free ? ACCESS_ENTITLED : ACCESS_FREE);
|
||||||
vo.setPlayUrl(signedUrl);
|
vo.setPlayUrl(signedUrl);
|
||||||
|
vo.setTracks(tracksOf(episode.getId(), signedUrl));
|
||||||
|
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
recordTicket(userId, drama.getId(), episode.getId(), signedUrl, deviceId, clientIp);
|
recordTicket(userId, drama.getId(), episode.getId(), signedUrl, deviceId, clientIp);
|
||||||
@@ -115,6 +117,29 @@ public class PlaybackService {
|
|||||||
return qualities;
|
return qualities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** AUTO 用主列表,其余档位用各自的 index.m3u8,地址都签名。 */
|
||||||
|
private List<PlayTrackVO> tracksOf(Long episodeId, String signedAuto) {
|
||||||
|
List<PlayTrackVO> tracks = new ArrayList<>();
|
||||||
|
tracks.add(track("AUTO", signedAuto));
|
||||||
|
for (MediaRendition rendition : transcodeService.listRenditions(episodeId)) {
|
||||||
|
if (!StringUtils.hasText(rendition.getPlayUrl())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!"ready".equalsIgnoreCase(rendition.getStatus())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tracks.add(track(rendition.getQuality(), signer.sign(rendition.getPlayUrl())));
|
||||||
|
}
|
||||||
|
return tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlayTrackVO track(String quality, String playUrl) {
|
||||||
|
PlayTrackVO track = new PlayTrackVO();
|
||||||
|
track.setQuality(quality);
|
||||||
|
track.setPlayUrl(playUrl);
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
private void recordTicket(Long userId, Long dramaId, Long episodeId, String playUrl,
|
private void recordTicket(Long userId, Long dramaId, Long episodeId, String playUrl,
|
||||||
String deviceId, String clientIp) {
|
String deviceId, String clientIp) {
|
||||||
PlaybackTicket ticket = new PlaybackTicket();
|
PlaybackTicket ticket = new PlaybackTicket();
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ public class PlayRespVO {
|
|||||||
private String playUrl;
|
private String playUrl;
|
||||||
/** 可用的清晰度档位 */
|
/** 可用的清晰度档位 */
|
||||||
private List<String> qualities;
|
private List<String> qualities;
|
||||||
|
/** 各档签名地址。AUTO 是主播放列表,其余是单档 HLS。 */
|
||||||
|
private List<PlayTrackVO> tracks;
|
||||||
/** 雪碧图 WebVTT 时间轴地址;没有产物时为 null */
|
/** 雪碧图 WebVTT 时间轴地址;没有产物时为 null */
|
||||||
private String thumbnailsUrl;
|
private String thumbnailsUrl;
|
||||||
/** 需要多少金币解锁 */
|
/** 需要多少金币解锁 */
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// Author: fruit.com.cn
|
||||||
|
|
||||||
|
package com.fruitshorts.playback.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/** 一档可切换的清晰度及其签名播放地址。 */
|
||||||
|
@Data
|
||||||
|
public class PlayTrackVO {
|
||||||
|
|
||||||
|
/** AUTO 或 360p/480p/720p/1080p。 */
|
||||||
|
private String quality;
|
||||||
|
|
||||||
|
/** 签名后的播放地址,客户端原样使用。 */
|
||||||
|
private String playUrl;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user