Skip to content

Commit

Permalink
Merge pull request #93 from mujik-tigers/feat/92-live-playing-video
Browse files Browse the repository at this point in the history
feat: 라이브 영상 저장 기능 구현
  • Loading branch information
ghkdgus29 authored May 26, 2024
2 parents 8118cf0 + 045cc21 commit 9855d9d
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 69 deletions.
8 changes: 5 additions & 3 deletions src/main/java/site/youtogether/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -34,10 +36,10 @@ public JedisConnectionFactory redisConnectionFactory() {
}

@Bean
public DefaultRedisScript<Void> batchRemoveScript() {
DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>();
public DefaultRedisScript<List> batchRemoveScript() {
DefaultRedisScript<List> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("script/batch-removal-operation.lua")));
redisScript.setResultType(Void.class);
redisScript.setResultType(List.class);
return redisScript;
}

Expand Down
46 changes: 46 additions & 0 deletions src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
30 changes: 30 additions & 0 deletions src/main/java/site/youtogether/playlist/PlayingLiveVideo.java
Original file line number Diff line number Diff line change
@@ -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);
}

}
55 changes: 13 additions & 42 deletions src/main/java/site/youtogether/playlist/PlayingVideo.java
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
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;
this.videoId = video.getVideoId();
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;
Expand Down Expand Up @@ -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);

}
4 changes: 4 additions & 0 deletions src/main/java/site/youtogether/playlist/Video.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ public Video(Long videoNumber, String videoId, long duration, String thumbnail,
this.channelTitle = channelTitle;
}

public boolean isLiveStreaming() {
return duration == 0;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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());
Expand All @@ -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());
Expand Down Expand Up @@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

}
11 changes: 9 additions & 2 deletions src/main/java/site/youtogether/util/DataCleaner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void> batchRemoveScript;
private final DefaultRedisScript<List> batchRemoveScript;
private final RedisTemplate<String, String> 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);
}
}

}
1 change: 1 addition & 0 deletions src/main/resources/script/batch-removal-operation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ for _, key in ipairs(userList) do
end
end

return eraseRoomList

23 changes: 8 additions & 15 deletions src/test/java/site/youtogether/util/DataCleanerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -40,7 +39,7 @@ class DataCleanerTest extends IntegrationTestSupport {
private StringRedisTemplate redisTemplate;

@Autowired
private DefaultRedisScript<Void> batchRemoveScript;
private DataCleaner dataCleaner;

@AfterEach
void clean() {
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 9855d9d

Please sign in to comment.