From 899e3d827cd119cfb1eb4fae0774483846c160e4 Mon Sep 17 00:00:00 2001 From: yeonise Date: Sat, 11 May 2024 14:57:42 +0900 Subject: [PATCH 1/7] =?UTF-8?q?log:=20=EC=9E=84=EC=8B=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../message/presentation/MessageEventListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/site/youtogether/message/presentation/MessageEventListener.java b/src/main/java/site/youtogether/message/presentation/MessageEventListener.java index 5d1a2b7..48a146b 100644 --- a/src/main/java/site/youtogether/message/presentation/MessageEventListener.java +++ b/src/main/java/site/youtogether/message/presentation/MessageEventListener.java @@ -37,7 +37,7 @@ public void handleWebSocketSubscriberListener(SessionSubscribeEvent event) { Long userId = (Long)headerAccessor.getSessionAttributes().get(USER_ID); User user = userStorage.findById(userId) .orElseThrow(UserNoExistenceException::new); - log.info("--USER {} 웹 소켓 구독 시작--", userId); + log.info("--USER {} ROOM {} 웹 소켓 구독 시작--", userId, roomCode); messageService.sendParticipants(roomCode); messageService.sendPlaylist(roomCode); @@ -53,7 +53,7 @@ public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) { User user = userStorage.findById(userId) .orElseThrow(UserNoExistenceException::new); - log.info("--USER {} 웹 소켓 커넥션 종료 시도--", userId); + log.info("--USER {} ROOM {} 웹 소켓 커넥션 종료 시도--", userId, roomCode); roomService.leave(userId); messageService.sendParticipants(roomCode); messageService.sendAlarm(new AlarmMessage(roomCode, "[알림] " + user.getNickname() + "님이 퇴장하셨습니다.")); From d014049b3e242ab0eb87eb19af0083d10436c305 Mon Sep 17 00:00:00 2001 From: Hyun Date: Thu, 23 May 2024 18:24:01 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20cors=20=ED=97=88=EC=9A=A9=20?= =?UTF-8?q?=ED=98=B8=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/site/youtogether/config/WebConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/site/youtogether/config/WebConfig.java b/src/main/java/site/youtogether/config/WebConfig.java index b323db8..4edf6a5 100644 --- a/src/main/java/site/youtogether/config/WebConfig.java +++ b/src/main/java/site/youtogether/config/WebConfig.java @@ -26,7 +26,11 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:3000", "https://you-together-web.vercel.app", "https://localhost:3001") + .allowedOrigins("http://localhost:3000", + "https://you-together-web.vercel.app", + "https://localhost:3001", + "https://you-together.site", + "https://www.you-together.site") .allowedMethods("*") .allowCredentials(true) .exposedHeaders(HttpHeaders.AUTHORIZATION) From 32a527cb8467f2ba7f1c7f453b2baa4789d1d96f Mon Sep 17 00:00:00 2001 From: Hyun Date: Thu, 23 May 2024 18:39:15 +0900 Subject: [PATCH 3/7] =?UTF-8?q?chore:=20cors=20=EC=86=8C=EC=BC=93=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=20=ED=98=B8=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/site/youtogether/config/WebSocketConfig.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/site/youtogether/config/WebSocketConfig.java b/src/main/java/site/youtogether/config/WebSocketConfig.java index 4a87b79..efa4bc2 100644 --- a/src/main/java/site/youtogether/config/WebSocketConfig.java +++ b/src/main/java/site/youtogether/config/WebSocketConfig.java @@ -27,7 +27,11 @@ public void configureMessageBroker(MessageBrokerRegistry registry) { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(STOMP_ENDPOINT) - .setAllowedOriginPatterns("http://localhost:3000", "https://you-together-web.vercel.app", "https://localhost:3001") + .setAllowedOriginPatterns("http://localhost:3000", + "https://you-together-web.vercel.app", + "https://localhost:3001", + "https://you-together.site", + "https://www.you-together.site") .addInterceptors(stompHandshakeInterceptor) .withSockJS(); } From fdf0ee258ec78e7b9d788b36c3203e85ba07743b Mon Sep 17 00:00:00 2001 From: Hyun Date: Thu, 27 Jun 2024 17:00:49 +0900 Subject: [PATCH 4/7] =?UTF-8?q?log:=20=EB=B0=A9=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=8B=9C=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=EB=A1=9C=20?= =?UTF-8?q?=EB=84=98=EC=96=B4=EC=98=A8=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../site/youtogether/room/presentation/RoomController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/site/youtogether/room/presentation/RoomController.java b/src/main/java/site/youtogether/room/presentation/RoomController.java index 4f62233..fce3b4e 100644 --- a/src/main/java/site/youtogether/room/presentation/RoomController.java +++ b/src/main/java/site/youtogether/room/presentation/RoomController.java @@ -16,6 +16,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import site.youtogether.room.application.RoomService; import site.youtogether.room.dto.ChangedRoomTitle; import site.youtogether.room.dto.NewRoom; @@ -30,12 +31,14 @@ @RestController @RequiredArgsConstructor +@Slf4j public class RoomController { private final RoomService roomService; @GetMapping("/rooms") public ResponseEntity> fetchRoomList(@PageableDefault Pageable pageable, @RequestParam(required = false) String keyword) { + log.info("넘어온 keyword:{}", keyword); RoomList roomList = roomService.fetchAll(pageable, keyword); return ResponseEntity.ok() From 1cd32ccdc41dc520b935c345b17ee042c89ef817 Mon Sep 17 00:00:00 2001 From: Hyun Date: Thu, 27 Jun 2024 17:33:43 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Revert=20"log:=20=EB=B0=A9=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=8B=9C=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0?= =?UTF-8?q?=EB=A1=9C=20=EB=84=98=EC=96=B4=EC=98=A8=20=ED=82=A4=EC=9B=8C?= =?UTF-8?q?=EB=93=9C=20=EB=A1=9C=EA=B9=85"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit fdf0ee258ec78e7b9d788b36c3203e85ba07743b. --- .../site/youtogether/room/presentation/RoomController.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/site/youtogether/room/presentation/RoomController.java b/src/main/java/site/youtogether/room/presentation/RoomController.java index fce3b4e..4f62233 100644 --- a/src/main/java/site/youtogether/room/presentation/RoomController.java +++ b/src/main/java/site/youtogether/room/presentation/RoomController.java @@ -16,7 +16,6 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import site.youtogether.room.application.RoomService; import site.youtogether.room.dto.ChangedRoomTitle; import site.youtogether.room.dto.NewRoom; @@ -31,14 +30,12 @@ @RestController @RequiredArgsConstructor -@Slf4j public class RoomController { private final RoomService roomService; @GetMapping("/rooms") public ResponseEntity> fetchRoomList(@PageableDefault Pageable pageable, @RequestParam(required = false) String keyword) { - log.info("넘어온 keyword:{}", keyword); RoomList roomList = roomService.fetchAll(pageable, keyword); return ResponseEntity.ok() From 4fd781c888601bc147cdedadabc20750f5196364 Mon Sep 17 00:00:00 2001 From: yeonise Date: Wed, 21 Aug 2024 19:33:02 +0900 Subject: [PATCH 6/7] refactor: User Builder --- src/main/java/site/youtogether/user/User.java | 42 +++++++++++++++---- .../interceptor/SessionCreateInterceptor.java | 15 ++++--- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/main/java/site/youtogether/user/User.java b/src/main/java/site/youtogether/user/User.java index bcd0d91..db79eb3 100644 --- a/src/main/java/site/youtogether/user/User.java +++ b/src/main/java/site/youtogether/user/User.java @@ -14,7 +14,6 @@ import com.redis.om.spring.annotations.Indexed; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import site.youtogether.exception.user.HigherOrEqualRoleChangeException; @@ -32,22 +31,49 @@ public class User { @Id private Long id; + private String nickname; + @Indexed private String currentRoomCode; @Indexed private boolean activate; - private String nickname; private Map history = new HashMap<>(); private Queue roomCodeQueue = new ArrayDeque<>(); - @Builder - private User(Long id, String nickname, String currentRoomCode, boolean activate) { - this.id = id; - this.nickname = nickname; - this.currentRoomCode = currentRoomCode; - this.activate = activate; + public static class Builder { + private final Long id; + private final String nickname; + + private String currentRoomCode = null; + private boolean activate = false; + + public Builder(Long id, String nickname) { + this.id = id; + this.nickname = nickname; + } + + public Builder currentRoomCode(String val) { + currentRoomCode = val; + return this; + } + + public Builder activate(boolean val) { + activate = val; + return this; + } + + public User build() { + return new User(this); + } + } + + private User(Builder builder) { + id = builder.id; + nickname = builder.nickname; + currentRoomCode = builder.currentRoomCode; + activate = builder.activate; } public String getCurrentRoomCode() { diff --git a/src/main/java/site/youtogether/util/interceptor/SessionCreateInterceptor.java b/src/main/java/site/youtogether/util/interceptor/SessionCreateInterceptor.java index 2c90af9..f6fd45d 100644 --- a/src/main/java/site/youtogether/util/interceptor/SessionCreateInterceptor.java +++ b/src/main/java/site/youtogether/util/interceptor/SessionCreateInterceptor.java @@ -57,19 +57,18 @@ private Long generateSession(HttpServletRequest request, HttpServletResponse res request.setAttribute(USER_ID, userId); generateCookie(response, newToken); - String randomNickname = RandomUtil.generateUserNickname(); - while (uniqueNicknameStorage.exist(randomNickname)) { - randomNickname = RandomUtil.generateUserNickname(); + String nickname = RandomUtil.generateUserNickname(); + while (uniqueNicknameStorage.exist(nickname)) { + nickname = RandomUtil.generateUserNickname(); } - User user = User.builder() - .id(userId) - .nickname(randomNickname) - .currentRoomCode(null) + User user = new User + .Builder(userId, nickname) .activate(true) .build(); + userStorage.save(user); - uniqueNicknameStorage.save(randomNickname); + uniqueNicknameStorage.save(nickname); return userId; } From 208a041ab2e430fddbc599d5a96e5a656899b6d9 Mon Sep 17 00:00:00 2001 From: yeonise Date: Sat, 31 Aug 2024 16:12:19 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=201=EC=B0=A8=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../message/VideoSyncInfoMessage.java | 39 +++++- .../presentation/MessageController.java | 6 +- .../site/youtogether/player/PlayingVideo.java | 47 +++++++ .../site/youtogether/player/VideoPlayer.java | 69 ++++++++++ .../player/application/SynchronizerTask.java | 36 ++++++ .../application/VideoPlayerService.java | 22 ++++ .../player/application/VideoSynchronizer.java | 49 +++++++ .../player/handler/EndStateHandler.java | 13 ++ .../player/handler/PauseStateHandler.java | 16 +++ .../player/handler/PlayStateHandler.java | 16 +++ .../player/handler/PlayerState.java | 20 +++ .../player/handler/PlayerStateHandler.java | 10 ++ .../player/handler/RateStateHandler.java | 16 +++ .../infrastructure/VideoPlayerStorage.java | 44 +++++++ .../youtogether/playlist/PlayerState.java | 7 - .../playlist/PlayingDefaultVideo.java | 43 ------- .../playlist/PlayingLiveVideo.java | 30 ----- .../youtogether/playlist/PlayingVideo.java | 92 ------------- .../site/youtogether/playlist/Playlist.java | 73 ++++++----- .../java/site/youtogether/playlist/Video.java | 35 ++--- .../application/PlayingVideoService.java | 34 ----- .../playlist/application/PlaylistService.java | 121 ++++++------------ .../youtogether/playlist/dto/NextVideo.java | 12 +- .../playlist/dto/PlaylistAddForm.java | 16 --- .../youtogether/playlist/dto/VideoForm.java | 25 ++++ .../youtogether/playlist/dto/VideoInfo.java | 25 ++-- .../youtogether/playlist/dto/VideoOrder.java | 7 +- .../infrastructure/PlayingVideoStorage.java | 34 ----- .../infrastructure/PlaylistStorage.java | 6 + .../presentation/PlaylistController.java | 6 +- src/main/java/site/youtogether/room/Room.java | 2 +- .../room/application/RoomService.java | 3 +- ...asswordInput.java => EnteredPassword.java} | 7 +- .../{TitleInput.java => EnteredTitle.java} | 7 +- .../site/youtogether/room/dto/RoomDetail.java | 2 +- .../youtogether/room/dto/RoomListDetail.java | 2 +- .../{KeywordInput.java => SearchKeyword.java} | 4 +- .../room/presentation/RoomController.java | 18 +-- src/main/java/site/youtogether/user/User.java | 18 +-- .../user/infrastructure/UserStorage.java | 5 + 40 files changed, 578 insertions(+), 459 deletions(-) create mode 100644 src/main/java/site/youtogether/player/PlayingVideo.java create mode 100644 src/main/java/site/youtogether/player/VideoPlayer.java create mode 100644 src/main/java/site/youtogether/player/application/SynchronizerTask.java create mode 100644 src/main/java/site/youtogether/player/application/VideoPlayerService.java create mode 100644 src/main/java/site/youtogether/player/application/VideoSynchronizer.java create mode 100644 src/main/java/site/youtogether/player/handler/EndStateHandler.java create mode 100644 src/main/java/site/youtogether/player/handler/PauseStateHandler.java create mode 100644 src/main/java/site/youtogether/player/handler/PlayStateHandler.java create mode 100644 src/main/java/site/youtogether/player/handler/PlayerState.java create mode 100644 src/main/java/site/youtogether/player/handler/PlayerStateHandler.java create mode 100644 src/main/java/site/youtogether/player/handler/RateStateHandler.java create mode 100644 src/main/java/site/youtogether/player/infrastructure/VideoPlayerStorage.java delete mode 100644 src/main/java/site/youtogether/playlist/PlayerState.java delete mode 100644 src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java delete mode 100644 src/main/java/site/youtogether/playlist/PlayingLiveVideo.java delete mode 100644 src/main/java/site/youtogether/playlist/PlayingVideo.java delete mode 100644 src/main/java/site/youtogether/playlist/application/PlayingVideoService.java delete mode 100644 src/main/java/site/youtogether/playlist/dto/PlaylistAddForm.java create mode 100644 src/main/java/site/youtogether/playlist/dto/VideoForm.java delete mode 100644 src/main/java/site/youtogether/playlist/infrastructure/PlayingVideoStorage.java rename src/main/java/site/youtogether/room/dto/{PasswordInput.java => EnteredPassword.java} (71%) rename src/main/java/site/youtogether/room/dto/{TitleInput.java => EnteredTitle.java} (77%) rename src/main/java/site/youtogether/room/dto/{KeywordInput.java => SearchKeyword.java} (79%) diff --git a/src/main/java/site/youtogether/message/VideoSyncInfoMessage.java b/src/main/java/site/youtogether/message/VideoSyncInfoMessage.java index 763a1b8..4a66228 100644 --- a/src/main/java/site/youtogether/message/VideoSyncInfoMessage.java +++ b/src/main/java/site/youtogether/message/VideoSyncInfoMessage.java @@ -1,11 +1,13 @@ package site.youtogether.message; +import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; -import site.youtogether.playlist.PlayerState; +import site.youtogether.player.PlayingVideo; +import site.youtogether.player.handler.PlayerState; -@AllArgsConstructor +@AllArgsConstructor(access = AccessLevel.PACKAGE) @Getter public class VideoSyncInfoMessage { @@ -19,4 +21,37 @@ public class VideoSyncInfoMessage { private final double playerCurrentTime; private final double playerRate; + public static VideoSyncInfoMessage ofPlay(PlayingVideo playingVideo, double currentTime) { + return new VideoSyncInfoMessage( + playingVideo.roomCode(), + playingVideo.number(), + playingVideo.id(), + PlayerState.PLAY, + currentTime, + playingVideo.rate() + ); + } + + public static VideoSyncInfoMessage ofPause(PlayingVideo playingVideo) { + return new VideoSyncInfoMessage( + playingVideo.roomCode(), + playingVideo.number(), + playingVideo.id(), + PlayerState.PAUSE, + playingVideo.startTime(), + playingVideo.rate() + ); + } + + public static VideoSyncInfoMessage ofEnd(PlayingVideo playingVideo) { + return new VideoSyncInfoMessage( + playingVideo.roomCode(), + playingVideo.number(), + playingVideo.id(), + PlayerState.END, + playingVideo.duration(), + playingVideo.rate() + ); + } + } diff --git a/src/main/java/site/youtogether/message/presentation/MessageController.java b/src/main/java/site/youtogether/message/presentation/MessageController.java index 3b84951..70eaeeb 100644 --- a/src/main/java/site/youtogether/message/presentation/MessageController.java +++ b/src/main/java/site/youtogether/message/presentation/MessageController.java @@ -13,7 +13,7 @@ import site.youtogether.message.ChatMessage; import site.youtogether.message.VideoSyncInfoMessage; import site.youtogether.message.application.MessageService; -import site.youtogether.playlist.application.PlayingVideoService; +import site.youtogether.player.application.VideoPlayerService; import site.youtogether.user.User; import site.youtogether.user.infrastructure.UserStorage; import site.youtogether.util.RandomUtil; @@ -24,7 +24,7 @@ public class MessageController { private final UserStorage userStorage; private final MessageService messageService; - private final PlayingVideoService playingVideoService; + private final VideoPlayerService videoPlayerService; @MessageMapping("/messages/chat") public void handleChatMessage(ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) { @@ -53,7 +53,7 @@ public void handleVideoSyncMessage(VideoSyncInfoMessage videoSyncInfoMessage, Si } videoSyncInfoMessage.setRoomCode(user.getCurrentRoomCode()); - playingVideoService.manageVideo(videoSyncInfoMessage); + videoPlayerService.control(videoSyncInfoMessage); } } diff --git a/src/main/java/site/youtogether/player/PlayingVideo.java b/src/main/java/site/youtogether/player/PlayingVideo.java new file mode 100644 index 0000000..d307d33 --- /dev/null +++ b/src/main/java/site/youtogether/player/PlayingVideo.java @@ -0,0 +1,47 @@ +package site.youtogether.player; + +import site.youtogether.playlist.Video; + +public record PlayingVideo( + String roomCode, + String id, + Long number, + double startTime, + double rate, + double duration) { + + private static final double ZERO_SECOND = 0.0; + private static final double DEFAULT_RATE = 1.0; + + public static PlayingVideo updateStartTime(PlayingVideo playingVideo, double startTime) { + return new PlayingVideo( + playingVideo.roomCode(), + playingVideo.id(), + playingVideo.number(), + startTime, + playingVideo.rate(), + playingVideo.duration()); + } + + public static PlayingVideo updateRate(PlayingVideo playingVideo, double rate) { + return new PlayingVideo( + playingVideo.roomCode(), + playingVideo.id(), + playingVideo.number(), + playingVideo.startTime(), + rate, + playingVideo.duration()); + } + + public static PlayingVideo from(String roomCode, Video video) { + return new PlayingVideo( + roomCode, + video.id(), + video.number(), + ZERO_SECOND, + DEFAULT_RATE, + video.duration() + ); + } + +} diff --git a/src/main/java/site/youtogether/player/VideoPlayer.java b/src/main/java/site/youtogether/player/VideoPlayer.java new file mode 100644 index 0000000..83612a1 --- /dev/null +++ b/src/main/java/site/youtogether/player/VideoPlayer.java @@ -0,0 +1,69 @@ +package site.youtogether.player; + +import static site.youtogether.player.handler.PlayerState.*; + +import java.util.Timer; + +import site.youtogether.player.application.VideoSynchronizer; +import site.youtogether.player.handler.PlayerState; + +public class VideoPlayer { + + private final String roomCode; + private final VideoSynchronizer videoSynchronizer; + private final Timer timer; + private PlayerState state; + private PlayingVideo playingVideo; + + public VideoPlayer(String roomCode, VideoSynchronizer videoSynchronizer) { + this.roomCode = roomCode; + this.videoSynchronizer = videoSynchronizer; + this.timer = new Timer(); + this.state = END; + } + + // 영상을 일시정지합니다 + public void pauseVideo(PlayingVideo playingVideo) { + if (playingVideo == null) + return; + + videoSynchronizer.pause(timer, playingVideo); + state = PAUSE; + } + + // 영상을 재생합니다 + public void playVideo(PlayingVideo playingVideo) { + switchVideo(playingVideo); + videoSynchronizer.play(timer, playingVideo); + state = PLAY; + } + + public void stopVideo() { + if (playingVideo != null) { + videoSynchronizer.stop(timer, playingVideo); + state = END; + playingVideo = null; + } + } + + public void switchVideo(PlayingVideo playingVideo) { + boolean wasPlaying = state == PLAY; + boolean isNewVideo = !this.playingVideo.number().equals(playingVideo.number()); + // 재생 중인 영상이 있다면 우선 중지합니다 + stopVideo(); + this.playingVideo = playingVideo; + + if (wasPlaying || isNewVideo) { + playVideo(playingVideo); + } + } + + public String getRoomCode() { + return this.roomCode; + } + + public PlayingVideo getPlayingVideo() { + return playingVideo; + } + +} diff --git a/src/main/java/site/youtogether/player/application/SynchronizerTask.java b/src/main/java/site/youtogether/player/application/SynchronizerTask.java new file mode 100644 index 0000000..c018a1d --- /dev/null +++ b/src/main/java/site/youtogether/player/application/SynchronizerTask.java @@ -0,0 +1,36 @@ +package site.youtogether.player.application; + +import java.util.TimerTask; + +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.player.PlayingVideo; + +public abstract class SynchronizerTask extends TimerTask { + + private static final double EMPTY = 0.0; + + private final PlayingVideo playingVideo; + private double currentTime; + + public SynchronizerTask(PlayingVideo playingVideo) { + this.playingVideo = playingVideo; + this.currentTime = playingVideo.startTime(); + } + + public VideoSyncInfoMessage getSyncMessage() { + return VideoSyncInfoMessage.ofPlay(playingVideo, currentTime); + } + + public boolean isLiveStream() { + return playingVideo.duration() == EMPTY; + } + + public void incrementTime() { + ++currentTime; + } + + public double getCurrentTime() { + return currentTime; + } + +} diff --git a/src/main/java/site/youtogether/player/application/VideoPlayerService.java b/src/main/java/site/youtogether/player/application/VideoPlayerService.java new file mode 100644 index 0000000..81590a6 --- /dev/null +++ b/src/main/java/site/youtogether/player/application/VideoPlayerService.java @@ -0,0 +1,22 @@ +package site.youtogether.player.application; + +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.player.VideoPlayer; +import site.youtogether.player.handler.PlayerState; +import site.youtogether.player.infrastructure.VideoPlayerStorage; + +@Service +@RequiredArgsConstructor +public class VideoPlayerService { + + private final VideoPlayerStorage videoPlayerStorage; + + public void control(VideoSyncInfoMessage message) { + VideoPlayer videoPlayer = videoPlayerStorage.findByRoomCode(message.getRoomCode()); + PlayerState.handlerOf(message.getPlayerState()).handle(videoPlayer, message); + } + +} diff --git a/src/main/java/site/youtogether/player/application/VideoSynchronizer.java b/src/main/java/site/youtogether/player/application/VideoSynchronizer.java new file mode 100644 index 0000000..5c1b1cd --- /dev/null +++ b/src/main/java/site/youtogether/player/application/VideoSynchronizer.java @@ -0,0 +1,49 @@ +package site.youtogether.player.application; + +import java.util.Timer; + +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.message.application.MessageService; +import site.youtogether.player.PlayingVideo; + +@Component +@RequiredArgsConstructor +public class VideoSynchronizer { + + private static final long NO_DELAY = 0; + private static final long ONE_SECOND = 1_000; + + private final MessageService messageService; + + public void pause(Timer timer, PlayingVideo playingVideo) { + timer.cancel(); + + // 일시정지 메세지를 1회 전송합니다 + messageService.sendVideoSyncInfo(VideoSyncInfoMessage.ofPause(playingVideo)); + } + + public void play(Timer timer, PlayingVideo playingVideo) { + // 1초마다 재생 메세지를 전송합니다 + timer.scheduleAtFixedRate(new SynchronizerTask(playingVideo) { + @Override + public void run() { + if (this.isLiveStream() || this.getCurrentTime() < playingVideo.duration()) { + messageService.sendVideoSyncInfo(this.getSyncMessage()); + } + + this.incrementTime(); + } + }, NO_DELAY, Math.round(ONE_SECOND / playingVideo.rate())); + } + + public void stop(Timer timer, PlayingVideo playingVideo) { + timer.cancel(); + + // 종료 메세지를 1회 전송합니다 + messageService.sendVideoSyncInfo(VideoSyncInfoMessage.ofEnd(playingVideo)); + } + +} diff --git a/src/main/java/site/youtogether/player/handler/EndStateHandler.java b/src/main/java/site/youtogether/player/handler/EndStateHandler.java new file mode 100644 index 0000000..5ab8651 --- /dev/null +++ b/src/main/java/site/youtogether/player/handler/EndStateHandler.java @@ -0,0 +1,13 @@ +package site.youtogether.player.handler; + +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.player.VideoPlayer; + +public class EndStateHandler implements PlayerStateHandler { + + @Override + public void handle(VideoPlayer videoPlayer, VideoSyncInfoMessage message) { + videoPlayer.stopVideo(); + } + +} diff --git a/src/main/java/site/youtogether/player/handler/PauseStateHandler.java b/src/main/java/site/youtogether/player/handler/PauseStateHandler.java new file mode 100644 index 0000000..0464b9e --- /dev/null +++ b/src/main/java/site/youtogether/player/handler/PauseStateHandler.java @@ -0,0 +1,16 @@ +package site.youtogether.player.handler; + +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.player.PlayingVideo; +import site.youtogether.player.VideoPlayer; + +public class PauseStateHandler implements PlayerStateHandler { + + @Override + public void handle(VideoPlayer videoPlayer, VideoSyncInfoMessage message) { + PlayingVideo playingVideo = PlayingVideo.updateStartTime(videoPlayer.getPlayingVideo(), message.getPlayerCurrentTime()); + + videoPlayer.pauseVideo(playingVideo); + } + +} diff --git a/src/main/java/site/youtogether/player/handler/PlayStateHandler.java b/src/main/java/site/youtogether/player/handler/PlayStateHandler.java new file mode 100644 index 0000000..36a735e --- /dev/null +++ b/src/main/java/site/youtogether/player/handler/PlayStateHandler.java @@ -0,0 +1,16 @@ +package site.youtogether.player.handler; + +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.player.PlayingVideo; +import site.youtogether.player.VideoPlayer; + +public class PlayStateHandler implements PlayerStateHandler { + + @Override + public void handle(VideoPlayer videoPlayer, VideoSyncInfoMessage message) { + PlayingVideo playingVideo = PlayingVideo.updateStartTime(videoPlayer.getPlayingVideo(), message.getPlayerCurrentTime()); + + videoPlayer.playVideo(playingVideo); + } + +} diff --git a/src/main/java/site/youtogether/player/handler/PlayerState.java b/src/main/java/site/youtogether/player/handler/PlayerState.java new file mode 100644 index 0000000..62a26db --- /dev/null +++ b/src/main/java/site/youtogether/player/handler/PlayerState.java @@ -0,0 +1,20 @@ +package site.youtogether.player.handler; + +public enum PlayerState { + + PLAY(new PlayStateHandler()), + PAUSE(new PauseStateHandler()), + END(new EndStateHandler()), + RATE(new RateStateHandler()); + + private final PlayerStateHandler handler; + + PlayerState(PlayerStateHandler handler) { + this.handler = handler; + } + + public static PlayerStateHandler handlerOf(PlayerState playerState) { + return playerState.handler; + } + +} diff --git a/src/main/java/site/youtogether/player/handler/PlayerStateHandler.java b/src/main/java/site/youtogether/player/handler/PlayerStateHandler.java new file mode 100644 index 0000000..5fb2026 --- /dev/null +++ b/src/main/java/site/youtogether/player/handler/PlayerStateHandler.java @@ -0,0 +1,10 @@ +package site.youtogether.player.handler; + +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.player.VideoPlayer; + +public interface PlayerStateHandler { + + void handle(VideoPlayer videoPlayer, VideoSyncInfoMessage message); + +} diff --git a/src/main/java/site/youtogether/player/handler/RateStateHandler.java b/src/main/java/site/youtogether/player/handler/RateStateHandler.java new file mode 100644 index 0000000..ef33a73 --- /dev/null +++ b/src/main/java/site/youtogether/player/handler/RateStateHandler.java @@ -0,0 +1,16 @@ +package site.youtogether.player.handler; + +import site.youtogether.message.VideoSyncInfoMessage; +import site.youtogether.player.PlayingVideo; +import site.youtogether.player.VideoPlayer; + +public class RateStateHandler implements PlayerStateHandler { + + @Override + public void handle(VideoPlayer videoPlayer, VideoSyncInfoMessage message) { + PlayingVideo playingVideo = PlayingVideo.updateRate(videoPlayer.getPlayingVideo(), message.getPlayerRate()); + + videoPlayer.switchVideo(playingVideo); + } + +} diff --git a/src/main/java/site/youtogether/player/infrastructure/VideoPlayerStorage.java b/src/main/java/site/youtogether/player/infrastructure/VideoPlayerStorage.java new file mode 100644 index 0000000..a536a35 --- /dev/null +++ b/src/main/java/site/youtogether/player/infrastructure/VideoPlayerStorage.java @@ -0,0 +1,44 @@ +package site.youtogether.player.infrastructure; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Repository; + +import lombok.RequiredArgsConstructor; +import site.youtogether.player.VideoPlayer; +import site.youtogether.player.application.VideoSynchronizer; + +@Repository +@RequiredArgsConstructor +public class VideoPlayerStorage { + + // key: String roomCode - value: VideoPlayer + private final Map storage = new ConcurrentHashMap<>(); + private final VideoSynchronizer videoSynchronizer; + + public boolean existsByRoomCode(String roomCode) { + return storage.containsKey(roomCode); + } + + public VideoPlayer findByRoomCode(String roomCode) { + return Optional.ofNullable(storage.get(roomCode)) + .orElseGet(() -> new VideoPlayer(roomCode, videoSynchronizer)); + } + + public VideoPlayer save(VideoPlayer videoPlayer) { + // VideoPlayer를 덮어 쓰기 전에 동작 중인 타이머가 있다면 종료해야 합니다 + deleteByRoomCode(videoPlayer.getRoomCode()); + storage.put(videoPlayer.getRoomCode(), videoPlayer); + + return videoPlayer; + } + + public void deleteByRoomCode(String roomCode) { + // VideoPlayer를 지울 때 동작 중인 타이머가 있다면 종료해야 합니다 + Optional.ofNullable(storage.remove(roomCode)) + .ifPresent(VideoPlayer::stopVideo); + } + +} diff --git a/src/main/java/site/youtogether/playlist/PlayerState.java b/src/main/java/site/youtogether/playlist/PlayerState.java deleted file mode 100644 index b8ff336..0000000 --- a/src/main/java/site/youtogether/playlist/PlayerState.java +++ /dev/null @@ -1,7 +0,0 @@ -package site.youtogether.playlist; - -public enum PlayerState { - - PLAY, PAUSE, END, RATE - -} diff --git a/src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java b/src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java deleted file mode 100644 index 893e3c9..0000000 --- a/src/main/java/site/youtogether/playlist/PlayingDefaultVideo.java +++ /dev/null @@ -1,43 +0,0 @@ -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) { - try { - playlistService.callNextVideoByTimer(roomCode); - } catch (PlaylistEmptyException ignored) { - } - timer.cancel(); - timer.purge(); - return; - } - messageService.sendVideoSyncInfo( - new VideoSyncInfoMessage(roomCode, videoNumber, 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 deleted file mode 100644 index 9292885..0000000 --- a/src/main/java/site/youtogether/playlist/PlayingLiveVideo.java +++ /dev/null @@ -1,30 +0,0 @@ -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, videoNumber, 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 deleted file mode 100644 index 237635b..0000000 --- a/src/main/java/site/youtogether/playlist/PlayingVideo.java +++ /dev/null @@ -1,92 +0,0 @@ -package site.youtogether.playlist; - -import java.util.Timer; - -import lombok.Getter; -import site.youtogether.exception.playlist.InvalidVideoRateException; -import site.youtogether.message.VideoSyncInfoMessage; -import site.youtogether.message.application.MessageService; -import site.youtogether.playlist.application.PlaylistService; - -@Getter -public abstract class PlayingVideo { - - protected PlayerState playerState; - protected final String roomCode; - protected final String videoId; - protected final Long videoNumber; - protected final String videoTitle; - protected final String channelTitle; - protected final String thumbnail; - protected final MessageService messageService; - protected final PlaylistService playlistService; - - 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.videoNumber = video.getVideoNumber(); - this.videoTitle = video.getVideoTitle(); - this.channelTitle = video.getChannelTitle(); - this.thumbnail = video.getThumbnail(); - - this.messageService = messageService; - this.playlistService = playlistService; - } - - public void startAt(double time) { - timer.cancel(); - timer.purge(); - currentTime = Math.round(time * 100) / 100.0; - playerState = PlayerState.PLAY; - - createTimer(playerRate); - } - - public void pauseAt(double time) { - timer.cancel(); - timer.purge(); - currentTime = Math.round(time * 100) / 100.0; - playerState = PlayerState.PAUSE; - - messageService.sendVideoSyncInfo( - new VideoSyncInfoMessage(roomCode, videoNumber, videoId, playerState, currentTime, playerRate) - ); - } - - public void stop() { - timer.cancel(); - timer.purge(); - playerState = PlayerState.END; - - messageService.sendVideoSyncInfo( - new VideoSyncInfoMessage(roomCode, videoNumber, videoId, playerState, currentTime, playerRate) - ); - } - - public void changeRate(double time, double playerRate) { - if (playerRate < 0.25 || playerRate > 2 || (int)(playerRate * 100) % 5 != 0) { - throw new InvalidVideoRateException(); - } - timer.cancel(); - timer.purge(); - - this.playerRate = playerRate; - this.timerPeriod = Math.round(1000 / playerRate); - - if (playerState == PlayerState.PLAY) { - createTimer(playerRate); - } else if (playerState == PlayerState.PAUSE) { - messageService.sendVideoSyncInfo( - new VideoSyncInfoMessage(roomCode, videoNumber, videoId, playerState, time, playerRate) - ); - } - } - - protected abstract void createTimer(double playerRate); - -} diff --git a/src/main/java/site/youtogether/playlist/Playlist.java b/src/main/java/site/youtogether/playlist/Playlist.java index 49dd10a..78d0ae0 100644 --- a/src/main/java/site/youtogether/playlist/Playlist.java +++ b/src/main/java/site/youtogether/playlist/Playlist.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import org.springframework.data.annotation.Id; @@ -16,57 +15,69 @@ import site.youtogether.exception.playlist.PlaylistEmptyException; @Document(value = "playlist") -@NoArgsConstructor(access = AccessLevel.PROTECTED) +@NoArgsConstructor(access = AccessLevel.PRIVATE) @Getter public class Playlist { + /** + * 재생 목록 + *

key : roomCode + *

- 재생 목록 조회 + *

- 재생 목록에 영상 추가 + *

- 재생 목록의 영상 순서 변경 + *

- 재생 목록의 영상 삭제 + */ + + private static final int FIRST = 0; + @Id private String roomCode; - private List