diff --git a/src/main/java/site/youtogether/config/RedisConfig.java b/src/main/java/site/youtogether/config/RedisConfig.java index c8fd219..c680c82 100644 --- a/src/main/java/site/youtogether/config/RedisConfig.java +++ b/src/main/java/site/youtogether/config/RedisConfig.java @@ -1,5 +1,7 @@ package site.youtogether.config; +import java.util.List; + import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,10 +36,10 @@ public JedisConnectionFactory redisConnectionFactory() { } @Bean - public DefaultRedisScript batchRemoveScript() { - DefaultRedisScript redisScript = new DefaultRedisScript<>(); + public DefaultRedisScript batchRemoveScript() { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/batch-removal-operation.lua"))); - redisScript.setResultType(Void.class); + redisScript.setResultType(List.class); return redisScript; } diff --git a/src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java b/src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java new file mode 100644 index 0000000..93b752e --- /dev/null +++ b/src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java @@ -0,0 +1,46 @@ +package site.youtogether.playlist; + +import java.util.Timer; +import java.util.TimerTask; + +import site.youtogether.exception.playlist.PlaylistEmptyException; +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.message.application.MessageService; +import site.youtogether.playlist.application.PlaylistService; + +public class PlayingDefaultVideo extends PlayingVideo { + + private final long totalTime; + + public PlayingDefaultVideo(String roomCode, Video video, MessageService messageService, PlaylistService playlistService) { + super(roomCode, video, messageService, playlistService); + this.totalTime = video.getDuration(); + } + + @Override + protected void createTimer(double playerRate) { + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (currentTime >= totalTime) { + messageService.sendVideoSyncInfo( + new VideoSyncInfoMessage(roomCode, videoId, PlayerState.END, totalTime, playerRate) + ); + try { + playlistService.callNextVideoByTimer(roomCode); + } catch (PlaylistEmptyException ignored) { + } + timer.cancel(); + timer.purge(); + return; + } + messageService.sendVideoSyncInfo( + new VideoSyncInfoMessage(roomCode, videoId, PlayerState.PLAY, currentTime, playerRate) + ); + currentTime += 1; + } + }, 0, timerPeriod); + } + +} diff --git a/src/main/java/site/youtogether/playlist/PlayingLiveVideo.java b/src/main/java/site/youtogether/playlist/PlayingLiveVideo.java new file mode 100644 index 0000000..db7b2b1 --- /dev/null +++ b/src/main/java/site/youtogether/playlist/PlayingLiveVideo.java @@ -0,0 +1,30 @@ +package site.youtogether.playlist; + +import java.util.Timer; +import java.util.TimerTask; + +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.message.application.MessageService; +import site.youtogether.playlist.application.PlaylistService; + +public class PlayingLiveVideo extends PlayingVideo { + + public PlayingLiveVideo(String roomCode, Video video, MessageService messageService, PlaylistService playlistService) { + super(roomCode, video, messageService, playlistService); + } + + @Override + protected void createTimer(double playerRate) { + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + messageService.sendVideoSyncInfo( + new VideoSyncInfoMessage(roomCode, videoId, PlayerState.PLAY, currentTime, playerRate) + ); + currentTime += 1; + } + }, 0, timerPeriod); + } + +} diff --git a/src/main/java/site/youtogether/playlist/PlayingVideo.java b/src/main/java/site/youtogether/playlist/PlayingVideo.java index a590e79..40b3b69 100644 --- a/src/main/java/site/youtogether/playlist/PlayingVideo.java +++ b/src/main/java/site/youtogether/playlist/PlayingVideo.java @@ -1,32 +1,28 @@ package site.youtogether.playlist; -import java.time.Duration; import java.util.Timer; -import java.util.TimerTask; import lombok.Getter; import site.youtogether.exception.playlist.InvalidVideoRateException; -import site.youtogether.exception.playlist.PlaylistEmptyException; import site.youtogether.message.VideoSyncInfoMessage; import site.youtogether.message.application.MessageService; import site.youtogether.playlist.application.PlaylistService; @Getter -public class PlayingVideo { +public abstract class PlayingVideo { - private final String roomCode; - private final String videoId; - private final String videoTitle; - private final String channelTitle; - private final String thumbnail; - private final long totalTime; - private final MessageService messageService; - private final PlaylistService playlistService; + protected final String roomCode; + protected final String videoId; + protected final String videoTitle; + protected final String channelTitle; + protected final String thumbnail; + protected final MessageService messageService; + protected final PlaylistService playlistService; - private double currentTime; - private Timer timer = new Timer(); - private double playerRate = 1.0; - private long timerPeriod = 1000; + protected double currentTime = 0.0; + protected Timer timer = new Timer(); + protected double playerRate = 1.0; + protected long timerPeriod = 1000; public PlayingVideo(String roomCode, Video video, MessageService messageService, PlaylistService playlistService) { this.roomCode = roomCode; @@ -34,8 +30,6 @@ public PlayingVideo(String roomCode, Video video, MessageService messageService, this.videoTitle = video.getVideoTitle(); this.channelTitle = video.getChannelTitle(); this.thumbnail = video.getThumbnail(); - this.totalTime = video.getDuration() > 0 ? video.getDuration() : Duration.ofDays(1).toSeconds(); - this.currentTime = 0.0; this.messageService = messageService; this.playlistService = playlistService; @@ -75,29 +69,6 @@ public void changeRate(double playerRate) { createTimer(playerRate); } - private void createTimer(double playerRate) { - timer = new Timer(); - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (currentTime >= totalTime) { - messageService.sendVideoSyncInfo( - new VideoSyncInfoMessage(roomCode, videoId, PlayerState.END, totalTime, playerRate) - ); - try { - playlistService.callNextVideoByTimer(roomCode); - } catch (PlaylistEmptyException ignored) { - } - timer.cancel(); - timer.purge(); - return; - } - messageService.sendVideoSyncInfo( - new VideoSyncInfoMessage(roomCode, videoId, PlayerState.PLAY, currentTime, playerRate) - ); - currentTime += 1; - } - }, 0, timerPeriod); - } + protected abstract void createTimer(double playerRate); } diff --git a/src/main/java/site/youtogether/playlist/Video.java b/src/main/java/site/youtogether/playlist/Video.java index 3de0f05..2027eda 100644 --- a/src/main/java/site/youtogether/playlist/Video.java +++ b/src/main/java/site/youtogether/playlist/Video.java @@ -23,4 +23,8 @@ public Video(Long videoNumber, String videoId, long duration, String thumbnail, this.channelTitle = channelTitle; } + public boolean isLiveStreaming() { + return duration == 0; + } + } diff --git a/src/main/java/site/youtogether/playlist/application/PlaylistService.java b/src/main/java/site/youtogether/playlist/application/PlaylistService.java index be640e4..05d6440 100644 --- a/src/main/java/site/youtogether/playlist/application/PlaylistService.java +++ b/src/main/java/site/youtogether/playlist/application/PlaylistService.java @@ -9,6 +9,8 @@ import site.youtogether.exception.user.UserNoExistenceException; import site.youtogether.exception.user.VideoEditDeniedException; import site.youtogether.message.application.MessageService; +import site.youtogether.playlist.PlayingDefaultVideo; +import site.youtogether.playlist.PlayingLiveVideo; import site.youtogether.playlist.PlayingVideo; import site.youtogether.playlist.Playlist; import site.youtogether.playlist.Video; @@ -45,7 +47,7 @@ public void addVideo(Long userId, PlaylistAddForm form) { if (!playingVideoStorage.existsById(user.getCurrentRoomCode())) { Video nextVideo = playlist.playNext(video.getVideoNumber()); - playingVideoStorage.saveAndPlay(new PlayingVideo(user.getCurrentRoomCode(), nextVideo, messageService, this)); + playingVideoStorage.saveAndPlay(createPlayingVideo(user.getCurrentRoomCode(), nextVideo)); messageService.sendStartVideoInfo(user.getCurrentRoomCode(), nextVideo.getVideoTitle(), nextVideo.getChannelTitle()); } playlistStorage.save(playlist); @@ -59,7 +61,7 @@ public void callNextVideoByTimer(String roomCode) { // PlayingVideo 타 playingVideoStorage.delete(roomCode); // 다음에 재생할 영상이 없더라도, 현재 재생중인 영상을 제거해야 하므로, delete 가 선행 Video nextVideo = playlist.playNextCallByTimer(); - playingVideoStorage.saveAndPlay(new PlayingVideo(roomCode, nextVideo, messageService, this)); + playingVideoStorage.saveAndPlay(createPlayingVideo(roomCode, nextVideo)); playlistStorage.save(playlist); messageService.sendStartVideoInfo(roomCode, nextVideo.getVideoTitle(), nextVideo.getChannelTitle()); @@ -78,7 +80,7 @@ public void playNextVideo(Long userId, Long videoNumber) { Video nextVideo = playlist.playNext(videoNumber); playingVideoStorage.delete(user.getCurrentRoomCode()); // 다음에 재생할 영상이 올바르지 않은 경우, 현재 재생중인 영상을 제거하면 안되므로, delete 가 후행 - playingVideoStorage.saveAndPlay(new PlayingVideo(user.getCurrentRoomCode(), nextVideo, messageService, this)); + playingVideoStorage.saveAndPlay(createPlayingVideo(user.getCurrentRoomCode(), nextVideo)); playlistStorage.save(playlist); messageService.sendStartVideoInfo(user.getCurrentRoomCode(), nextVideo.getVideoTitle(), nextVideo.getChannelTitle()); @@ -128,4 +130,12 @@ private Video createVideo(PlaylistAddForm form) { .build(); } + private PlayingVideo createPlayingVideo(String roomCode, Video nextVideo) { + if (nextVideo.isLiveStreaming()) { + return new PlayingLiveVideo(roomCode, nextVideo, messageService, this); + } + + return new PlayingDefaultVideo(roomCode, nextVideo, messageService, this); + } + } diff --git a/src/main/java/site/youtogether/playlist/infrastructure/PlayingVideoStorage.java b/src/main/java/site/youtogether/playlist/infrastructure/PlayingVideoStorage.java index 6c90ecf..3ca64e6 100644 --- a/src/main/java/site/youtogether/playlist/infrastructure/PlayingVideoStorage.java +++ b/src/main/java/site/youtogether/playlist/infrastructure/PlayingVideoStorage.java @@ -27,8 +27,8 @@ public void saveAndPlay(PlayingVideo playingVideo) { } public void delete(String roomCode) { - PlayingVideo removed = storage.remove(roomCode); - removed.stop(); + Optional.ofNullable(storage.remove(roomCode)) + .ifPresent(PlayingVideo::stop); } } diff --git a/src/main/java/site/youtogether/util/DataCleaner.java b/src/main/java/site/youtogether/util/DataCleaner.java index 8293b9f..6ee1f9b 100644 --- a/src/main/java/site/youtogether/util/DataCleaner.java +++ b/src/main/java/site/youtogether/util/DataCleaner.java @@ -10,18 +10,25 @@ import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; +import site.youtogether.playlist.infrastructure.PlayingVideoStorage; @Component @RequiredArgsConstructor public class DataCleaner { - private final DefaultRedisScript batchRemoveScript; + private final DefaultRedisScript batchRemoveScript; private final RedisTemplate redisTemplate; + private final PlayingVideoStorage playingVideoStorage; @Scheduled(cron = "0 0 6 * * *", zone = "Asia/Seoul") public void clean() { - redisTemplate.execute(batchRemoveScript, + List erasedRoomKey = redisTemplate.execute(batchRemoveScript, List.of("site.youtogether.room.RoomIdx", "site.youtogether.user.UserIdx", USER_NICKNAME_SET)); + + for (int i = 1; i < erasedRoomKey.size(); i++) { + String erasedRoomCode = erasedRoomKey.get(i).toString().substring("room:".length()); + playingVideoStorage.delete(erasedRoomCode); + } } } diff --git a/src/main/resources/script/batch-removal-operation.lua b/src/main/resources/script/batch-removal-operation.lua index 6f86507..5b7f321 100644 --- a/src/main/resources/script/batch-removal-operation.lua +++ b/src/main/resources/script/batch-removal-operation.lua @@ -26,4 +26,5 @@ for _, key in ipairs(userList) do end end +return eraseRoomList diff --git a/src/test/java/site/youtogether/util/DataCleanerTest.java b/src/test/java/site/youtogether/util/DataCleanerTest.java index 7ae7adc..c045516 100644 --- a/src/test/java/site/youtogether/util/DataCleanerTest.java +++ b/src/test/java/site/youtogether/util/DataCleanerTest.java @@ -11,7 +11,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.core.script.DefaultRedisScript; import site.youtogether.IntegrationTestSupport; import site.youtogether.playlist.Playlist; @@ -40,7 +39,7 @@ class DataCleanerTest extends IntegrationTestSupport { private StringRedisTemplate redisTemplate; @Autowired - private DefaultRedisScript batchRemoveScript; + private DataCleaner dataCleaner; @AfterEach void clean() { @@ -66,8 +65,7 @@ void test1() { roomStorage.save(room); // when - redisTemplate.execute(batchRemoveScript, - List.of("site.youtogether.room.RoomIdx", "site.youtogether.user.UserIdx", USER_NICKNAME_SET)); + dataCleaner.clean(); // then Room result = roomStorage.findById(room.getCode()).get(); @@ -96,8 +94,7 @@ void test2() { redisTemplate.opsForList().rightPush(CHAT_PREFIX + room.getCode(), "chatting3~"); // when - redisTemplate.execute(batchRemoveScript, - List.of("site.youtogether.room.RoomIdx", "site.youtogether.user.UserIdx", USER_NICKNAME_SET)); + dataCleaner.clean(); // then assertThat(roomStorage.existsById(room.getCode())).isFalse(); @@ -126,8 +123,7 @@ void test3() { playlistStorage.save(playlist); // when - redisTemplate.execute(batchRemoveScript, - List.of("site.youtogether.room.RoomIdx", "site.youtogether.user.UserIdx", USER_NICKNAME_SET)); + dataCleaner.clean(); // then Room result = roomStorage.findById(room.getCode()).get(); @@ -151,8 +147,7 @@ void test4() { uniqueNicknameStorage.save(user.getNickname()); // when - redisTemplate.execute(batchRemoveScript, - List.of("site.youtogether.room.RoomIdx", "site.youtogether.user.UserIdx", USER_NICKNAME_SET)); + dataCleaner.clean(); // then assertThat(uniqueNicknameStorage.exist(user.getNickname())).isTrue(); @@ -175,8 +170,7 @@ void test5() { uniqueNicknameStorage.save(user.getNickname()); // when - redisTemplate.execute(batchRemoveScript, - List.of("site.youtogether.room.RoomIdx", "site.youtogether.user.UserIdx", USER_NICKNAME_SET)); + dataCleaner.clean(); // then assertThat(uniqueNicknameStorage.exist(user.getNickname())).isFalse(); @@ -195,10 +189,9 @@ void test6() { userStorage.save(user); uniqueNicknameStorage.save(user.getNickname()); - + // when - redisTemplate.execute(batchRemoveScript, - List.of("site.youtogether.room.RoomIdx", "site.youtogether.user.UserIdx", USER_NICKNAME_SET)); + dataCleaner.clean(); // then User result = userStorage.findById(user.getId()).get(); diff --git a/src/test/java/site/youtogether/util/aop/ConcurrencyHandlingAspectTest.java b/src/test/java/site/youtogether/util/aop/ConcurrencyHandlingAspectTest.java index cd0e7d5..c785a87 100644 --- a/src/test/java/site/youtogether/util/aop/ConcurrencyHandlingAspectTest.java +++ b/src/test/java/site/youtogether/util/aop/ConcurrencyHandlingAspectTest.java @@ -20,7 +20,7 @@ import site.youtogether.exception.playlist.InvalidVideoNumberException; import site.youtogether.message.ChatHistory; import site.youtogether.message.application.MessageService; -import site.youtogether.playlist.PlayingVideo; +import site.youtogether.playlist.PlayingDefaultVideo; import site.youtogether.playlist.Playlist; import site.youtogether.playlist.Video; import site.youtogether.playlist.application.PlaylistService; @@ -291,7 +291,7 @@ private Room createRoom(int capacity, int videoCount) { .videoId("videoId" + 9999) .duration(100000) .build(); - playingVideoStorage.saveAndPlay(new PlayingVideo(room.getCode(), video, messageService, playlistService)); + playingVideoStorage.saveAndPlay(new PlayingDefaultVideo(room.getCode(), video, messageService, playlistService)); return room; }