feat(media): 支持后台转码配置与缩略图轴
This commit is contained in:
1 parent
70da939a58
commit
316baf28d6
39 files changed
+1523
-38
No files matched your search
+13
-4
@@ -15,6 +15,7 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
@@ -55,7 +56,9 @@ class AppPlaybackContractTest {
|
||||
@Test
|
||||
void should_return_free_access_when_episode_is_free() throws Exception {
|
||||
given(playbackService.play(isNull(), eq(11L), any(), any()))
|
||||
.willReturn(ticket(11L, true, PlaybackService.ACCESS_FREE, "/files/ep.mp4?s=1"));
|
||||
.willReturn(ticket(11L, true, PlaybackService.ACCESS_FREE,
|
||||
"/files/ep.mp4?s=1",
|
||||
"/files/transcoded/11/thumbnails/thumbnails.vtt?e=1&s=sig"));
|
||||
|
||||
mockMvc.perform(get("/app/episodes/11/play").header("X-Device-Id", "device-9"))
|
||||
.andExpect(status().isOk())
|
||||
@@ -66,13 +69,15 @@ class AppPlaybackContractTest {
|
||||
.andExpect(jsonPath("$.data.accessible").value(true))
|
||||
.andExpect(jsonPath("$.data.accessType").value(PlaybackService.ACCESS_FREE))
|
||||
.andExpect(jsonPath("$.data.playUrl").value("/files/ep.mp4?s=1"))
|
||||
.andExpect(jsonPath("$.data.thumbnailsUrl")
|
||||
.value("/files/transcoded/11/thumbnails/thumbnails.vtt?e=1&s=sig"))
|
||||
.andExpect(jsonPath("$.data.likeCount").value("88"))
|
||||
.andExpect(jsonPath("$.data.commentCount").value("0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_return_coin_required_when_episode_is_locked_and_not_entitled() throws Exception {
|
||||
PlayRespVO locked = ticket(12L, false, PlaybackService.ACCESS_COIN_REQUIRED, "");
|
||||
PlayRespVO locked = ticket(12L, false, PlaybackService.ACCESS_COIN_REQUIRED, "", null);
|
||||
locked.setUnlockCoin(60);
|
||||
given(playbackService.play(isNull(), eq(12L), any(), any())).willReturn(locked);
|
||||
|
||||
@@ -81,13 +86,15 @@ class AppPlaybackContractTest {
|
||||
.andExpect(jsonPath("$.data.accessible").value(false))
|
||||
.andExpect(jsonPath("$.data.accessType").value(PlaybackService.ACCESS_COIN_REQUIRED))
|
||||
.andExpect(jsonPath("$.data.playUrl").value(""))
|
||||
.andExpect(jsonPath("$.data.thumbnailsUrl").value(nullValue()))
|
||||
.andExpect(jsonPath("$.data.unlockCoin").value(60));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_pass_episode_id_and_device_header_when_play_is_requested() throws Exception {
|
||||
given(playbackService.play(isNull(), eq(13L), eq("device-9"), any()))
|
||||
.willReturn(ticket(13L, true, PlaybackService.ACCESS_FREE, "/files/ep13.mp4?s=1"));
|
||||
.willReturn(ticket(13L, true, PlaybackService.ACCESS_FREE,
|
||||
"/files/ep13.mp4?s=1", null));
|
||||
|
||||
mockMvc.perform(get("/app/episodes/13/play").header("X-Device-Id", "device-9"))
|
||||
.andExpect(status().isOk());
|
||||
@@ -95,7 +102,8 @@ class AppPlaybackContractTest {
|
||||
verify(playbackService).play(isNull(), eq(13L), eq("device-9"), any());
|
||||
}
|
||||
|
||||
private PlayRespVO ticket(Long episodeId, boolean accessible, String accessType, String playUrl) {
|
||||
private PlayRespVO ticket(Long episodeId, boolean accessible, String accessType,
|
||||
String playUrl, String thumbnailsUrl) {
|
||||
PlayRespVO vo = new PlayRespVO();
|
||||
vo.setEpisodeId(episodeId);
|
||||
vo.setDramaId(7L);
|
||||
@@ -105,6 +113,7 @@ class AppPlaybackContractTest {
|
||||
vo.setAccessible(accessible);
|
||||
vo.setAccessType(accessType);
|
||||
vo.setPlayUrl(playUrl);
|
||||
vo.setThumbnailsUrl(thumbnailsUrl);
|
||||
vo.setQualities(List.of("AUTO"));
|
||||
vo.setUnlockCoin(0);
|
||||
vo.setPreviewSeconds(0);
|
||||
|
||||
+10
-1
@@ -12,6 +12,7 @@ import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.content.service.EpisodeService;
|
||||
import com.fruitshorts.interaction.service.CommentService;
|
||||
import com.fruitshorts.media.service.PlaybackSigner;
|
||||
import com.fruitshorts.media.service.ThumbnailUrlResolver;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -35,6 +36,7 @@ class AppCatalogServiceTest {
|
||||
private EpisodeService episodeService;
|
||||
private DramaCategoryService dramaCategoryService;
|
||||
private CommentService commentService;
|
||||
private ThumbnailUrlResolver thumbnailUrlResolver;
|
||||
private AppCatalogService catalogService;
|
||||
|
||||
@BeforeEach
|
||||
@@ -44,9 +46,11 @@ class AppCatalogServiceTest {
|
||||
BannerService bannerService = mock(BannerService.class);
|
||||
dramaCategoryService = mock(DramaCategoryService.class);
|
||||
PlaybackSigner playbackSigner = mock(PlaybackSigner.class);
|
||||
thumbnailUrlResolver = mock(ThumbnailUrlResolver.class);
|
||||
given(thumbnailUrlResolver.resolveAll(any())).willReturn(Map.of());
|
||||
commentService = mock(CommentService.class);
|
||||
catalogService = new AppCatalogService(dramaService, episodeService, bannerService,
|
||||
dramaCategoryService, playbackSigner, commentService);
|
||||
dramaCategoryService, playbackSigner, commentService, thumbnailUrlResolver);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -66,6 +70,8 @@ class AppCatalogServiceTest {
|
||||
given(commentService.countByDrama(42L)).willReturn(96L);
|
||||
given(commentService.countByEpisodes(List.of(11L, 22L)))
|
||||
.willReturn(Map.of(11L, 2L, 22L, 4L));
|
||||
given(thumbnailUrlResolver.resolveAll(List.of(11L, 22L)))
|
||||
.willReturn(Map.of(22L, "/files/transcoded/22/thumbnails/thumbnails.vtt"));
|
||||
|
||||
AppDramaDetailVO detail = catalogService.dramaDetail(42L);
|
||||
|
||||
@@ -75,7 +81,10 @@ class AppCatalogServiceTest {
|
||||
assertThat(detail.getCommentCount()).isEqualTo(96L);
|
||||
assertThat(detail.getEpisodes().get(0).getLikeCount()).isEqualTo(66L);
|
||||
assertThat(detail.getEpisodes().get(0).getCommentCount()).isEqualTo(4L);
|
||||
assertThat(detail.getEpisodes().get(0).getThumbnailsUrl())
|
||||
.isEqualTo("/files/transcoded/22/thumbnails/thumbnails.vtt");
|
||||
verify(commentService).countByDrama(42L);
|
||||
verify(thumbnailUrlResolver).resolveAll(List.of(11L, 22L));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-1
@@ -12,6 +12,7 @@ import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaReviewRecordMapper;
|
||||
import com.fruitshorts.content.service.DramaCategoryService;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.content.service.EpisodePriceSync;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -41,7 +42,8 @@ class DramaServiceFavoriteCountTest {
|
||||
dramaMapper,
|
||||
mock(DramaEpisodeMapper.class),
|
||||
mock(DramaReviewRecordMapper.class),
|
||||
mock(DramaCategoryService.class));
|
||||
mock(DramaCategoryService.class),
|
||||
mock(EpisodePriceSync.class));
|
||||
given(dramaMapper.update(isNull(), any())).willReturn(1);
|
||||
ArgumentCaptor<Wrapper<Drama>> captor = ArgumentCaptor.forClass(Wrapper.class);
|
||||
|
||||
|
||||
+3
-1
@@ -11,6 +11,7 @@ import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaReviewRecordMapper;
|
||||
import com.fruitshorts.content.service.DramaCategoryService;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.content.service.EpisodePriceSync;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -45,7 +46,8 @@ class DramaServiceFeedTest {
|
||||
dramaMapper,
|
||||
mock(DramaEpisodeMapper.class),
|
||||
mock(DramaReviewRecordMapper.class),
|
||||
mock(DramaCategoryService.class));
|
||||
mock(DramaCategoryService.class),
|
||||
mock(EpisodePriceSync.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-1
@@ -12,6 +12,7 @@ import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaReviewRecordMapper;
|
||||
import com.fruitshorts.content.service.DramaCategoryService;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.content.service.EpisodePriceSync;
|
||||
import com.fruitshorts.content.vo.DramaSaveReqVO;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -46,7 +47,8 @@ class DramaServiceK88Test {
|
||||
dramaMapper,
|
||||
mock(DramaEpisodeMapper.class),
|
||||
mock(DramaReviewRecordMapper.class),
|
||||
mock(DramaCategoryService.class));
|
||||
mock(DramaCategoryService.class),
|
||||
mock(EpisodePriceSync.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ import com.fruitshorts.content.dal.mapper.DramaMapper;
|
||||
import com.fruitshorts.content.dal.mapper.DramaReviewRecordMapper;
|
||||
import com.fruitshorts.content.service.DramaCategoryService;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.content.service.EpisodePriceSync;
|
||||
import com.fruitshorts.content.vo.DramaPageReqVO;
|
||||
import com.fruitshorts.content.vo.DramaRespVO;
|
||||
import com.fruitshorts.content.vo.DramaSaveReqVO;
|
||||
@@ -54,7 +55,8 @@ class DramaServiceMultiTagTest {
|
||||
dramaMapper,
|
||||
mock(DramaEpisodeMapper.class),
|
||||
mock(DramaReviewRecordMapper.class),
|
||||
categoryService);
|
||||
categoryService,
|
||||
mock(EpisodePriceSync.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+26
-1
@@ -6,6 +6,7 @@ import com.fruitshorts.content.dal.entity.DramaEpisode;
|
||||
import com.fruitshorts.content.service.DramaService;
|
||||
import com.fruitshorts.content.service.EpisodeService;
|
||||
import com.fruitshorts.media.service.PlaybackSigner;
|
||||
import com.fruitshorts.media.service.ThumbnailUrlResolver;
|
||||
import com.fruitshorts.media.service.TranscodeService;
|
||||
import com.fruitshorts.playback.dal.entity.PlaybackTicket;
|
||||
import com.fruitshorts.playback.dal.mapper.PlaybackTicketMapper;
|
||||
@@ -42,6 +43,7 @@ class PlaybackServiceTest {
|
||||
private PlaybackSigner signer;
|
||||
private EntitlementPort entitlementPort;
|
||||
private TranscodeService transcodeService;
|
||||
private ThumbnailUrlResolver thumbnailUrlResolver;
|
||||
private PlaybackService playbackService;
|
||||
|
||||
@BeforeEach
|
||||
@@ -52,10 +54,12 @@ class PlaybackServiceTest {
|
||||
signer = mock(PlaybackSigner.class);
|
||||
entitlementPort = mock(EntitlementPort.class);
|
||||
transcodeService = mock(TranscodeService.class);
|
||||
thumbnailUrlResolver = mock(ThumbnailUrlResolver.class);
|
||||
playbackService = new PlaybackService(episodeService, dramaService, ticketMapper, signer,
|
||||
entitlementPort, transcodeService);
|
||||
entitlementPort, transcodeService, thumbnailUrlResolver);
|
||||
given(dramaService.getPublished(anyLong())).willReturn(drama());
|
||||
given(transcodeService.listRenditions(anyLong())).willReturn(List.of());
|
||||
given(thumbnailUrlResolver.resolve(anyLong())).willReturn(null);
|
||||
given(signer.sign(anyString())).willReturn("/files/episode/1.mp4?e=1&s=sig");
|
||||
}
|
||||
|
||||
@@ -72,6 +76,27 @@ class PlaybackServiceTest {
|
||||
verify(ticketMapper, never()).insert(any(PlaybackTicket.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_return_signed_thumbnails_url_when_thumbnail_exists() {
|
||||
given(episodeService.getPublished(11L)).willReturn(episode(11L, 1, 0, "/files/episode/1.mp4"));
|
||||
given(thumbnailUrlResolver.resolve(11L))
|
||||
.willReturn("/files/transcoded/1/thumbnails/thumbnails.vtt?e=1&s=sig");
|
||||
|
||||
PlayRespVO resp = playbackService.play(null, 11L, "device-9", "203.0.113.8");
|
||||
|
||||
assertThat(resp.getThumbnailsUrl())
|
||||
.isEqualTo("/files/transcoded/1/thumbnails/thumbnails.vtt?e=1&s=sig");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_return_null_thumbnails_url_when_thumbnail_is_absent() {
|
||||
given(episodeService.getPublished(11L)).willReturn(episode(11L, 1, 0, "/files/episode/1.mp4"));
|
||||
|
||||
PlayRespVO resp = playbackService.play(null, 11L, "device-9", "203.0.113.8");
|
||||
|
||||
assertThat(resp.getThumbnailsUrl()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_return_coin_required_when_episode_is_locked_and_not_entitled() {
|
||||
given(episodeService.getPublished(12L)).willReturn(episode(12L, 0, 60, "/files/episode/2.mp4"));
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
package com.fruitshorts.app.service;
|
||||
|
||||
import com.fruitshorts.media.dal.entity.MediaThumbnail;
|
||||
import com.fruitshorts.media.service.MediaStorageService;
|
||||
import com.fruitshorts.media.service.PlaybackSigner;
|
||||
import com.fruitshorts.media.service.ThumbnailUrlResolver;
|
||||
import com.fruitshorts.media.service.TranscodeService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* 缩略图 objectKey 到公开签名地址的转换。
|
||||
*/
|
||||
class ThumbnailUrlResolverTest {
|
||||
|
||||
private TranscodeService transcodeService;
|
||||
private MediaStorageService storageService;
|
||||
private PlaybackSigner signer;
|
||||
private ThumbnailUrlResolver resolver;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
transcodeService = mock(TranscodeService.class);
|
||||
storageService = mock(MediaStorageService.class);
|
||||
signer = mock(PlaybackSigner.class);
|
||||
resolver = new ThumbnailUrlResolver(transcodeService, storageService, signer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_sign_vtt_public_url_when_ready_thumbnail_exists() {
|
||||
MediaThumbnail thumbnail = new MediaThumbnail();
|
||||
thumbnail.setVttObjectKey("transcoded/88/thumbnails/thumbnails.vtt");
|
||||
given(transcodeService.findReadyThumbnail(11L)).willReturn(thumbnail);
|
||||
given(storageService.toPublicUrl(thumbnail.getVttObjectKey()))
|
||||
.willReturn("/files/transcoded/88/thumbnails/thumbnails.vtt");
|
||||
given(signer.sign("/files/transcoded/88/thumbnails/thumbnails.vtt"))
|
||||
.willReturn("/files/transcoded/88/thumbnails/thumbnails.vtt?e=1&s=sig");
|
||||
|
||||
assertThat(resolver.resolve(11L))
|
||||
.isEqualTo("/files/transcoded/88/thumbnails/thumbnails.vtt?e=1&s=sig");
|
||||
verify(signer).sign("/files/transcoded/88/thumbnails/thumbnails.vtt");
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_return_null_when_thumbnail_record_is_absent() {
|
||||
assertThat(resolver.resolve(12L)).isNull();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user