Skip to content

Commit

Permalink
Basics #2 PR #6
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrBodunov authored Feb 25, 2025
2 parents 4ee83a5 + a941092 commit ed823b9
Show file tree
Hide file tree
Showing 12 changed files with 364 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# mse-2025-template
# mse-2025-template
52 changes: 52 additions & 0 deletions playground/dolotov/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Screen Recorder Extension (Прототип)

Это расширение для браузера Chrome, которое позволяет записывать экран и звук с микрофона. Записи сохраняются локально, а также отправляются на сервер.

---

## Как запустить проект

### Предварительные требования

1. **Установите Docker и Docker Compose.**

2. **Склонируйте папку `playground`**.

---

### Запуск проекта

1. **Перейдите в папку `server`**:
```bash
cd server
```

2. **Запустите проект с помощью Docker Compose**:
```bash
docker-compose up --build
```

3. **Установите расширение в Google Chrome**:
- Откройте `chrome://extensions/`.
- Включите режим разработчика.
- Нажмите "Load unpacked" (Загрузить распакованное расширение) и выберите папку `client`.

---

### Использование расширения

1. **Нажмите на иконку расширения**:
- Откроется вкладка для записи экрана.

2. **Введите имя пользователя**:
- В поле "Имя пользователя" введите ваше имя.

3. **Начните запись**:
- Нажмите кнопку "Начать запись".
- Выберите экран или окно для записи, а также предоставьте доступ к микрофону.

4. **Остановите запись**:
- Нажмите кнопку "Остановить запись".
- Видео сохранится на ваш компьютер и отправится на сервер.

---
3 changes: 3 additions & 0 deletions playground/dolotov/client/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.create({ url: chrome.runtime.getURL("index.html") });
});
21 changes: 21 additions & 0 deletions playground/dolotov/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=700, height=600, initial-scale=1.0">
<title>Screen Recorder</title>
<link rel="icon" type="image/png" href="static/icon.png">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="container">
<h1>Screen Recorder</h1>
<div class="input-group">
<label for="username">Имя пользователя:</label>
<input type="text" id="username" placeholder="№гр_Фамилия_Имя">
</div>
<button id="recordButton">Начать запись</button>
</div>
<script src="index.js"></script>
</body>
</html>
124 changes: 124 additions & 0 deletions playground/dolotov/client/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
document.getElementById('recordButton').onclick = async function () {
const username = document.getElementById('username').value;
if (!username) {
alert('Пожалуйста, введите ваше имя.');
return;
}

if (this.textContent === 'Начать запись') {
localStorage.setItem('username', username);

// Запрашиваем доступ к экрану и микрофону
chrome.desktopCapture.chooseDesktopMedia(["screen", "window"], async (streamId) => {
if (!streamId) {
alert('Запись экрана отменена.');
return;
}

try {
// Получаем медиапоток с экрана и микрофона
const screenStream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: streamId
}
}
});

const microphoneStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: false
});

// Объединяем потоки
const combinedStream = new MediaStream([
...screenStream.getVideoTracks(),
...microphoneStream.getAudioTracks()
]);

// Создаем MediaRecorder
mediaRecorder = new MediaRecorder(combinedStream, {
mimeType: 'video/webm; codecs=vp9,opus'
});
mediaRecorder.ondataavailable = handleDataAvailable;
mediaRecorder.start();
startTime = new Date();

this.textContent = 'Остановить запись';
this.classList.add('stop-button');
} catch (error) {
console.error('Ошибка при получении доступа к медиаустройствам:', error);
alert('Не удалось начать запись. Проверьте разрешения.');
}
});
} else {
// Останавливаем запись
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
this.textContent = 'Начать запись';
this.classList.remove('stop-button');
}
}
};

let mediaRecorder;
let recordedChunks = [];
let startTime;

// Обработчик данных записи
function handleDataAvailable(event) {
if (event.data.size > 0) {
recordedChunks.push(event.data);
const username = localStorage.getItem('username');
const formattedDate = formatDate(startTime);
const filename = `${username}_${formattedDate}.webm`;
const blob = new Blob(recordedChunks, { type: 'video/webm' });

saveVideoLocally(blob, filename);
sendVideoToServer(blob, filename);
recordedChunks = [];
}
}

// Форматирование даты
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day}_${hours}-${minutes}-${seconds}`;
}

// Сохранение видео локально
function saveVideoLocally(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 100);
}

// Отправка видео на сервер
function sendVideoToServer(blob, filename) {
let formData = new FormData();
formData.append('file', blob, filename);
fetch('http://localhost:5000/upload', {
method: 'POST',
body: formData
}).then(response => response.json())
.then(success => {
console.log('Видео успешно загружено:', success);
}).catch(error => {
console.error('Ошибка при загрузке видео:', error);
});
}
22 changes: 22 additions & 0 deletions playground/dolotov/client/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"manifest_version": 3,
"name": "Screen Recorder",
"version": "1.0",
"permissions": ["desktopCapture", "storage", "audioCapture"],
"background": {
"service_worker": "background.js"
},
"action": {
"default_icon": {
"16": "static/icon.png",
"48": "static/icon.png",
"128": "static/icon.png"
}
},
"web_accessible_resources": [
{
"resources": ["index.html", "style.css", "index.js", "static/icon.png"],
"matches": ["<all_urls>"]
}
]
}
Binary file added playground/dolotov/client/static/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions playground/dolotov/client/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f9f9f9;
font-family: Arial, sans-serif;
}

.container {
text-align: center;
width: 100%;
max-width: 400px;
padding: 20px;
box-sizing: border-box;
}

h1 {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
}

.input-group {
margin-bottom: 20px;
text-align: left;
}

label {
font-size: 16px;
font-weight: bold;
display: block;
margin-bottom: 5px;
}

input {
width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
box-sizing: border-box;
}

button {
width: 100%;
padding: 15px;
font-size: 18px;
color: white;
background-color: #4CAF50;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}

button:hover {
opacity: 0.9;
}

button.stop-button {
background-color: #ff4d4d;
}

button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
11 changes: 11 additions & 0 deletions playground/dolotov/server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "app.py"]
31 changes: 31 additions & 0 deletions playground/dolotov/server/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from flask import Flask, request, jsonify
from pymongo import MongoClient
import os

app = Flask(__name__)

client = MongoClient('mongodb://mongo:27017/')
db = client.screen_recorder

@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400

file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400

try:
file_path = os.path.join('/data', file.filename)
file.save(file_path)

db.recordings.insert_one({'filename': file.filename, 'path': file_path})

return jsonify({'success': True}), 200
except Exception as e:
print(f"Ошибка при сохранении файла или записи в базу данных: {e}")
return jsonify({'error': 'Internal server error'}), 500

if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
27 changes: 27 additions & 0 deletions playground/dolotov/server/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3'
services:
mongo:
image: mongo:8.0
container_name: mongo
volumes:
- mongo_data:/data/db
networks:
- backend

app:
build: .
ports:
- "5000:5000"
volumes:
- ./data:/data
depends_on:
- mongo
networks:
- backend

volumes:
mongo_data:

networks:
backend:
driver: bridge
2 changes: 2 additions & 0 deletions playground/dolotov/server/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask==3.1.0
flask-pymongo==3.0.1

0 comments on commit ed823b9

Please sign in to comment.