Skip to content
@Haru-Film

Haru Film

하루 필름 : 영상 일기 스트리밍 서비스
  • Korea, South

🎬 Haru Film (하루 필름)

하루 필름 로고



🔥 서비스 소개

Haru Film은 일상의 소중한 순간을 기록하고 공유하는 영상 일기 스트리밍 플랫폼입니다.

바쁜 현대 사회 속에서 '진짜 나'를 돌아보는 시간은 점점 줄어들고 있습니다. 텍스트 기반의 일기는 감정과 분위기를 온전히 담아내기 어렵다는 한계가 있습니다. 저희는 아티스트 빌리 아일리시(Billie Eilish)의 인터뷰 영상에서 영감을 받아, 누구나 자신만의 영상 인터뷰를 통해 현재를 기록하고 과거를 생생하게 회고할 수 있는 서비스를 기획하게 되었습니다.

주요 기능

기능 설명
🎥 영상 일기 기록 매일 제공되는 3개의 질문에 답하며 하루를 영상으로 기록합니다.
🎞️ Resumable Upload 네트워크 단절 상황에서도 중단된 지점부터 업로드 재개 가능합니다.
☁️ 비동기 인코딩 업로드된 영상을 360p/720p/1080p HLS 포맷으로 자동 변환합니다. 다양한 해상도로 변환하는 CPU 부하가 큰 인코딩 작업은 RabbitMQ를 통해 비동기적으로 처리됩니다.
👨🏻‍💻 적응형 스트리밍 네트워크 상황에 따라 최적의 화질을 자동 선택하여 끊김 없는 재생이 가능합니다.
📅 타임스탬프 & 회고 캘린더를 통해 과거 영상을 조회하고, 질문별 타임스탬프를 통해 원하는 답변 구간으로 즉시 이동합니다.
🔔 푸시 알림 영상 업로드 완료, 인코딩 완료, 일기 작성 리마인드 등을 FCM 푸시 알림으로 제공합니다.



🏗️ System Architecture

서비스 아키텍처



🧰 Technical Challenges

Challenge 1: 네트워크 단절을 극복하는 Resumable Upload

문제 상황 (Problem) : 모바일 환경 특성상 지하철 등에서 네트워크가 끊기면, 고용량 영상 업로드가 처음부터 다시 시작되어야 하는 문제가 있었습니다.

해결 방안 (Solution) :

  1. Chunk 분할: 클라이언트에서 파일을 일정 크기의 조각(Chunk)으로 분할합니다.
  2. 멀티파트 업로드: 대용량 파일도 안정적으로 업로드합니다.
  3. Presigned URL: 각 조각별로 보안 서명된 URL을 발급받아 S3로 직접 전송합니다. (서버 부하 감소)
  4. 상태 추적: localStorage에 업로드 성공한 조각 정보를 기록합니다.
  5. 재개(Resume): 네트워크 재연결 시, 실패하거나 전송되지 않은 조각부터 업로드를 이어갑니다.
  6. 업로드 실패 시 자동 롤백: 부분 데이터 자동 정리합니다.



영상 업로드 Flow Diagram

sequenceDiagram
    participant Client as 클라이언트
    participant Browser as Browser<br/>(localStorage)
    participant APIServer as API Server
    participant S3 as AWS S3
    
    Client->>Client: 1. 파일을 5MB 청크로 분할
    Client->>APIServer: 2. POST /uploads/multi-parts/initiate
    APIServer->>S3: 3. CreateMultipartUpload
    APIServer-->>Client: 4. uploadId 반환
    Client->>Browser: 5. 진행 상태 저장
    
    loop 각 청크별 업로드
        Client->>APIServer: 6. GET /uploads/multi-parts/part
        APIServer-->>Client: 7. Presigned URL
        Client->>S3: 8. PUT (직접 업로드)
        
        alt 성공
            S3-->>Client: 9. 200 OK
            Client->>Browser: 10. 완료 청크 기록
        else 네트워크 단절
            Note over Client: 재연결 대기
            Client->>Client: 11. 미완료 청크부터 재개
        end
    end
    
    Client->>APIServer: 12. POST /uploads/multi-parts/complete
    APIServer->>S3: 13. CompleteMultipartUpload
    APIServer->>DB: 14. Video 레코드 생성
    APIServer->>RabbitMQ: 15. 인코딩 작업 발행
Loading
Challenge 2: 스트리밍 및 인코딩 서비스

문제 상황 (Problem) : 업로드한 대용량의 영상을 단순 다운로드 방식으로 재생할 경우, 초기 로딩이 느리고 구간 이동이 불가능하며 서버 자원 소모가 큰 비효율적인 구조가 발생합니다. 또한 영상 스트리밍 중, 네트워크 환경에 따라 심각한 버퍼링이 발생해 사용자 경험을 저하하는 문제가 일어날 수 있습니다.

해결 방안 (Solution) : 사용자가 업로드한 원본 영상을 FFmpeg 기반 HLS 스트리밍 포맷으로 자동 변환하여 모든 네트워크 환경에서 끊김 없이 재생 가능한 구조를 설계했습니다.

  • 인코딩 파이프라인 자동화
    1. S3에 업로드된 원본 영상 다운로드
    2. FFmpeg로 .ts 조각과 master.m3u8 파일 생성 (HLS 표준 방식)
    3. 360p / 720p / 1080p 등 여러 해상도로 자동 변환(ABR)
    4. 변환된 파일을 S3 업로드
  • 네트워크 환경에 따른 최적 화질 제공(Adaptive Bitrate Streaming)
    • HLS 기반으로 사용자의 인터넷 속도에 따라 재생 화질이 실시간으로 자동 전환되어 느린 네트워크에서도 끊김 없이 빠른 로딩 가능
    • 초기 로딩 속도 대폭 개선
  • 모든 기기와 브라우저에서 동일하게 재생되는 표준 스트리밍 구조
    • HLS는 iOS / Android / PC / 모든 브라우저에서 표준적으로 지원
  • 사용자 경험 개선 경과
    • Buffering Time 100% 감소(395.8ms -> 0ms)
    • Seek Latency 58.6% 단축 (6,525ms -> 2,686ms)
    • Data Usage 42.5% 절감



스트리밍 flow diagram

sequenceDiagram
    participant User as 사용자
    participant Browser as 브라우저<br/>(HLS.js)
    participant S3 as AWS S3<br/>(Cloud Front CDN)
    participant EncodingServer as 인코딩 서버
    
    Note over User,EncodingServer: 비디오 업로드 → 인코딩 → 배포
    
    EncodingServer->>S3: 다양한 화질로 변환<br/>(360p, 720p, 1080p)
    EncodingServer->>S3: playlist.m3u8 배포<br/>(각 화질별 세그먼트 목록)
    
    Note over User,EncodingServer: 사용자 재생
    
    User->>Browser: 재생 요청
    Browser->>S3: GET playlist.m3u8<br/>(네트워크에 맞는 화질 선택)
    S3-->>Browser: 메타데이터 반환
    
    Browser->>S3: Range Request로<br/>첫 세그먼트만 요청<br/>bytes=0-1999999
    S3-->>Browser: 첫 세그먼트 전송
    Browser-->>User: 즉시 재생 시작
    
    par 재생 중 진행
        Browser->>Browser: 다음 세그먼트 미리 로드
        User->>Browser: 3분 지점으로 Seek
        Browser->>S3: 해당 시간의 세그먼트<br/>Range Request
        S3-->>Browser: 해당 부분만 전송
    end
    
    Note over Browser: 네트워크 상황에 따라<br/>화질 자동 조정
Loading
Challenge 3: Message Queue를 활용한 비동기 인코딩 처리

문제 상황 (Problem) : 고화질 영상을 다양한 해상도(ABR 적용 등)로 변환하는 인코딩 작업은 CPU 부하가 매우 큽니다. API 서버가 이 작업을 직접 처리할 경우, 요청이 몰리면 서버가 멈추거나 타임아웃이 발생할 위험이 있었습니다.

해결 방안 (Solution) : RabbitMQ를 도입하여 API 서버와 인코딩 서버를 물리적으로 분리하여 인코딩 서버에서 비동기적으로 처리됩니다.

  1. 요청 폭주에도 유실 없는 작업 처리 가능
  2. 백프레셔 구조로 안정성 확보
  3. api-server CPU 사용률 90% -> 20%
  4. 추후 인코딩 서버를 독립적으로 스케일 아웃할 수 있는 구조
  5. 추후 인코딩 실패 시 Dead Letter Queue를 통해 인코딩 실패 시 재시도 로직 도입 용이



최종 영상 인코딩 Flow Diagram

sequenceDiagram
    participant Client as 클라이언트
    participant API as API Server
    participant MQ as RabbitMQ
    participant Encoder as Encoding Server
    participant S3 as AWS S3
    participant DB as MySQL
    participant FCM as Firebase FCM
    
    Client->>API: 1. 업로드 완료 요청
    API->>DB: 2. Video 레코드 생성 (PENDING)
    API->>MQ: 3. 인코딩 작업 발행
    API->>FCM: 4. 업로드 완료 알림
    API-->>Client: 5. 즉시 응답
    
    MQ->>Encoder: 6. 메시지 소비
    Encoder->>S3: 7. 원본 영상 다운로드
    Encoder->>Encoder: 8. FFmpeg 인코딩<br/>(360p/720p/1080p)
    Encoder->>S3: 9. HLS 파일 업로드
    Encoder->>Encoder: 10. 임시 파일 삭제
    Encoder->>MQ: 11. 완료 메시지 발행
    
    MQ->>API: 12. 완료 메시지 소비
    API->>DB: 13. Video 상태 변경 (COMPLETE)
    API->>FCM: 14. 인코딩 완료 알림
    FCM-->>Client: 15. 푸시 알림 수신
Loading
Challenge 4: 자막 합성 전략

문제 상황 (Problem) : 초기에는 영상 파일 자체에 질문 자막을 합성(Burning)하는 방식을 고려했습니다.

  1. <video> 프레임을 <canvas>로 복사
  2. 텍스트 합성 후 canvas.captureStream()으로 녹화

하지만 이 방식은 브라우저가 GPU 가속(VPU)을 사용하지 못하고 CPU 기반 소프트웨어 인코딩을 강제하여, 심각한 프레임 저하가 발생했습니다.

해결 방안 (Solution) : DOM Overlay 방식을 도입하여 녹화 파이프라인과 렌더링 파이프라인을 분리했습니다.

  1. Stream 녹화: 자막 합성 없이 카메라 원본 스트림을 하드웨어 가속을 받아 녹화합니다.
  2. 메타데이터 동기화: 질문별 타임스탬프를 별도로 저장합니다.
  3. 실시간 렌더링: 재생 시 ontimeupdate 이벤트를 통해 영상 위에 HTML로 자막을 동적으로 렌더링합니다.

성능 개선 결과 (Benchmark)

측정 지표 DOM Overlay Canvas (Legacy) 개선율
FPS (Frame Rate) 119.7 ± 0.2 30.8 ± 2.5 +288% (약 3.8배)
효율성 (FPS/CPU) 23.47 6.04 +288%
  • 렌더링 병목을 제거하여 저사양 기기에서도 고주사율 녹화를 가능하게 했습니다.
  • 자막 On/Off 기능 및 추후 STT(Speech to Text) 등 확장성을 확보했습니다.
Challenge 5: 전역 커스텀 에러 핸들링

모든 API에서 동일한 에러 응답 형식 제공하고, 도메인별 커스텀 에러를 정의합니다.


에러 처리 컨벤션

  1. 모든 에러는 CustomError 사용
    import { UserNotFoundError } from '../errors/CustomError.js';
    throw new UserNotFoundError('사용자를 찾을 수 없습니다.');
  2. Controller는 next(error) 호출
    try {
        const result = await this.business.doSomething();
        res.status(200).json(result);
    } catch (error) {
        next(error);
    }
  3. 도메인별 에러 클래스 우선 사용
    • UserNotFoundError (자동으로 에러 코드 USER_NOT_FOUND 설정)
    • NotFoundError('message', 'USER_NOT_FOUND') (수동 입력)
  4. 구체적인 에러 메시지
    • 'uploadId, parts, uploadDate가 필요합니다.'
    • '파라미터 누락'
Challenge 6: Token 기반의 FCM 푸시 알림

하루필름 서비스는 다음과 같은 상황에 사용자에게 푸시 알림을 전송합니다.

  1. 사용자에게 매일 20시 영상 업로드 리마인드 알림
    • 당일 영상을 촬영하지 않은 사용자 대상으로 전송
    • 오늘의 질문 리스트 제공
  2. 영상 업로드 성공/실패 알림
  3. 영상 인코딩 작업 성공/실패 알림



하루 필름 푸시 알림 특징

  • 알림 오류가 메인 로직에 영향이 없습니다. API 응답과의 관계없는 비동기로 진행됩니다.
  • 인코딩 및 업로드 성공/실패 모두 알림을 제공하기에, 사용자가 항상 결과 인지 가능합니다.
  • 500개 토큰 배치 처리를 통해 알림을 나눠서 전송하기에, 사용자 수나 디바이스 수가 많아도 알림 기능을 지원 가능합니다.
  • 모든 동작 추적하기 위해, 상세하게 로깅을 진행했습니다.
  • 모든 등록 기기에 푸시 알림이 전송되며, 무효화된 토큰을 자동으로 삭제하여 관리합니다.



fcm



🌱 팀 소개

이름 학과 이메일 역할
김휘래(@rlagnlfo1004) 소프트웨어학과 [email protected] BackEnd Developer
박윤서(@Meon-ji) 사이버보안학과 [email protected] BackEnd Developer
전민규(@Mango-Juice) 소프트웨어학과 [email protected] FrontEnd Developer



✅ 협업 방식

효율적인 협업을 위해 GitLab을 활용한 명확한 컨벤션을 수립하여 진행했습니다.

1. Issue 규칙

컨벤션

구분 규칙
Issue 유형 feature, bug, refactor 등 라벨 지정
Title 형식 [Feat] 한 일, [Bug] 어떤 문제, [Refactor] 리팩토링 내용
Template 각 유형(feature / bug / refactor)에 맞는 템플릿 형식 유지
Task 작성 작업 상세 내용은 체크박스로 작성하여 추적 가능하게 유지



예시

  • Feature Issue 예시
    Title: [Feat] 영상 업로드 API 구현
    
    ## 💡 설명
    > 추가하려는 기능에 대해 간결하게 설명해주세요
    
    ## ✅ 작업 상세 내용
    - [ ] presigned URL 발급 API 구현
    - [ ] 업로드 검증 로직 추가
    - [ ] 파일 확장자 필터링 추가
    
    ## 📝 추가 설명
    없음
    
    ## 📚 참고 자료
    없음
    
  • Bug Issue 예시
    Title: [Bug] 키스톤 토큰 관련 버그 발견
    
    ## 💡 설명
    > 어떤 버그인지 간단하게 설명해주세요
    
    ## 🚨 상황
    로그인 후 토큰이 정상적으로 발급되지 않음
    
    ## 💥 실제 결과
    서버에서 401 Unauthorized 반환
    
    ## 🖥️ 예상 결과
    정상적으로 토큰 발급 및 인증 가능해야 함
    
    ## 📚 참고 자료
    없음
    
    
2. Branch 규칙

컨벤션

구분 규칙
브랜치 구조 feat/#이슈번호/작업내용
Prefix feat, fix, refactor, docs 등 issue type과 일치
Issue 연동 브랜치명에 GitLab issue number 반드시 포함



예시

feat/#75/createProjectForm
fix/#102/loginFailure
refactor/#88/encodingServiceLogic

3. Commit 규칙

컨벤션

구분 규칙
형식 {type}: {message}
Type feat, fix, docs, style, refactor, test, chore, design
Message 한글로 작성, 파일명/디렉토리명 명시 금지
공백 규칙 : 뒤에만 공백 존재



예시

feat: 예약 기능 validation 추가
fix: 로그인 실패 처리 로직 수정
refactor: 인코딩 서비스 코드 구조 재정비
docs: README API 문서 업데이트

4. Merge Request 규칙

컨벤션

구분 규칙
Title [Feat] 작업 내용 #이슈번호
MR Template default 템플릿 형식 유지
관련 이슈 #이슈번호 반드시 연결
설명 작업 요약 + 변경 사항 명확히 기재



예시

Title: [Feat] Vercel 배포 스크립트 및 Git Action 추가 #64

## 🔗 관련 이슈
> #64

## 💡 작업 내용
- Vercel 배포 스크립트 추가
- main 브랜치 push 시 자동 배포되는 GitHub Action 설정

## 📝 추가 설명
없음

## 📚 참고 자료
없음

Popular repositories Loading

  1. .github .github Public

    소개 페이지

  2. backend backend Public

    매일 영상으로 남기는 일기장, "오늘 나의 일기"의 백엔드 저장소

    JavaScript

  3. frontend frontend Public

    매일 영상으로 남기는 일기장, "오늘 나의 일기"의 프론트엔드 저장소

    TypeScript

Repositories

Showing 3 of 3 repositories

Top languages

Loading…

Most used topics

Loading…