aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/videos/+video-watch/video-download.component.html30
-rw-r--r--client/src/app/videos/+video-watch/video-download.component.ts (renamed from client/src/app/videos/+video-watch/video-magnet.component.ts)7
-rw-r--r--client/src/app/videos/+video-watch/video-magnet.component.html20
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html6
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts8
-rw-r--r--client/src/app/videos/+video-watch/video-watch.module.ts4
-rw-r--r--client/src/assets/player/peertube-videojs-plugin.ts7
-rw-r--r--server.ts44
-rw-r--r--server/models/video/video-interface.ts2
-rw-r--r--server/models/video/video.ts75
-rw-r--r--server/tests/api/multiple-pods.ts2
-rw-r--r--server/tests/api/single-pod.ts2
-rw-r--r--shared/models/videos/video.model.ts2
13 files changed, 123 insertions, 86 deletions
diff --git a/client/src/app/videos/+video-watch/video-download.component.html b/client/src/app/videos/+video-watch/video-download.component.html
new file mode 100644
index 000000000..ddc57e999
--- /dev/null
+++ b/client/src/app/videos/+video-watch/video-download.component.html
@@ -0,0 +1,30 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
2 <div class="modal-dialog">
3 <div class="modal-content modal-lg">
4
5 <div class="modal-header">
6 <button type="button" class="close" aria-label="Close" (click)="hide()">
7 <span aria-hidden="true">&times;</span>
8 </button>
9 <h4 class="modal-title">Download</h4>
10 </div>
11
12 <div class="modal-body">
13 <div *ngFor="let file of video.files" class="resolution-block">
14 <label>{{ file.resolutionLabel }}</label>
15 <a class="btn btn-default " target="_blank" [href]="file.torrentUrl">
16 <span class="glyphicon glyphicon-download"></span>
17 Torrent file
18 </a>
19 <a class="btn btn-default" target="_blank" [href]="file.fileUrl">
20 <span class="glyphicon glyphicon-download"></span>
21 Download
22 </a>
23
24 <!-- Don't display magnet URI for now, this is not compatible with most torrent clients -->
25 <!--<input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="file.magnetUri" />-->
26 </div>
27 </div>
28 </div>
29 </div>
30</div>
diff --git a/client/src/app/videos/+video-watch/video-magnet.component.ts b/client/src/app/videos/+video-watch/video-download.component.ts
index f9432e92c..22149aa6b 100644
--- a/client/src/app/videos/+video-watch/video-magnet.component.ts
+++ b/client/src/app/videos/+video-watch/video-download.component.ts
@@ -5,10 +5,11 @@ import { ModalDirective } from 'ngx-bootstrap/modal'
5import { Video } from '../shared' 5import { Video } from '../shared'
6 6
7@Component({ 7@Component({
8 selector: 'my-video-magnet', 8 selector: 'my-video-download',
9 templateUrl: './video-magnet.component.html' 9 templateUrl: './video-download.component.html',
10 styles: [ '.resolution-block { margin-top: 20px; }' ]
10}) 11})
11export class VideoMagnetComponent { 12export class VideoDownloadComponent {
12 @Input() video: Video = null 13 @Input() video: Video = null
13 14
14 @ViewChild('modal') modal: ModalDirective 15 @ViewChild('modal') modal: ModalDirective
diff --git a/client/src/app/videos/+video-watch/video-magnet.component.html b/client/src/app/videos/+video-watch/video-magnet.component.html
deleted file mode 100644
index 484280c45..000000000
--- a/client/src/app/videos/+video-watch/video-magnet.component.html
+++ /dev/null
@@ -1,20 +0,0 @@
1<div bsModal #modal="bs-modal" class="modal" tabindex="-1">
2 <div class="modal-dialog">
3 <div class="modal-content modal-lg">
4
5 <div class="modal-header">
6 <button type="button" class="close" aria-label="Close" (click)="hide()">
7 <span aria-hidden="true">&times;</span>
8 </button>
9 <h4 class="modal-title">Magnet Uri</h4>
10 </div>
11
12 <div class="modal-body">
13 <div *ngFor="let file of video.files">
14 <label>{{ file.resolutionLabel }}</label>
15 <input #magnetUriInput (click)="magnetUriInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="file.magnetUri" />
16 </div>
17 </div>
18 </div>
19 </div>
20</div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index 88863131a..5d5827344 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -71,8 +71,8 @@
71 </li> 71 </li>
72 72
73 <li role="menuitem"> 73 <li role="menuitem">
74 <a class="dropdown-item" title="Get magnet URI" href="#" (click)="showMagnetUriModal($event)"> 74 <a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)">
75 <span class="glyphicon glyphicon-magnet"></span> Magnet 75 <span class="glyphicon glyphicon-download-alt"></span> Download
76 </a> 76 </a>
77 </li> 77 </li>
78 78
@@ -179,6 +179,6 @@
179 179
180<ng-template [ngIf]="video !== null"> 180<ng-template [ngIf]="video !== null">
181 <my-video-share #videoShareModal [video]="video"></my-video-share> 181 <my-video-share #videoShareModal [video]="video"></my-video-share>
182 <my-video-magnet #videoMagnetModal [video]="video"></my-video-magnet> 182 <my-video-download #videoDownloadModal [video]="video"></my-video-download>
183 <my-video-report #videoReportModal [video]="video"></my-video-report> 183 <my-video-report #videoReportModal [video]="video"></my-video-report>
184</ng-template> 184</ng-template>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index bd98e877c..651298c14 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -10,7 +10,7 @@ import { MetaService } from '@ngx-meta/core'
10import { NotificationsService } from 'angular2-notifications' 10import { NotificationsService } from 'angular2-notifications'
11 11
12import { AuthService, ConfirmService } from '../../core' 12import { AuthService, ConfirmService } from '../../core'
13import { VideoMagnetComponent } from './video-magnet.component' 13import { VideoDownloadComponent } from './video-download.component'
14import { VideoShareComponent } from './video-share.component' 14import { VideoShareComponent } from './video-share.component'
15import { VideoReportComponent } from './video-report.component' 15import { VideoReportComponent } from './video-report.component'
16import { Video, VideoService } from '../shared' 16import { Video, VideoService } from '../shared'
@@ -23,7 +23,7 @@ import { UserVideoRateType, VideoRateType } from '../../../../../shared'
23 styleUrls: [ './video-watch.component.scss' ] 23 styleUrls: [ './video-watch.component.scss' ]
24}) 24})
25export class VideoWatchComponent implements OnInit, OnDestroy { 25export class VideoWatchComponent implements OnInit, OnDestroy {
26 @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent 26 @ViewChild('videoDownloadModal') videoDownloadModal: VideoDownloadComponent
27 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent 27 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
28 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent 28 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
29 29
@@ -160,9 +160,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
160 this.videoShareModal.show() 160 this.videoShareModal.show()
161 } 161 }
162 162
163 showMagnetUriModal (event: Event) { 163 showDownloadModal (event: Event) {
164 event.preventDefault() 164 event.preventDefault()
165 this.videoMagnetModal.show() 165 this.videoDownloadModal.show()
166 } 166 }
167 167
168 isUserLoggedIn () { 168 isUserLoggedIn () {
diff --git a/client/src/app/videos/+video-watch/video-watch.module.ts b/client/src/app/videos/+video-watch/video-watch.module.ts
index 5f20b171e..c6c1344ce 100644
--- a/client/src/app/videos/+video-watch/video-watch.module.ts
+++ b/client/src/app/videos/+video-watch/video-watch.module.ts
@@ -7,7 +7,7 @@ import { SharedModule } from '../../shared'
7import { VideoWatchComponent } from './video-watch.component' 7import { VideoWatchComponent } from './video-watch.component'
8import { VideoReportComponent } from './video-report.component' 8import { VideoReportComponent } from './video-report.component'
9import { VideoShareComponent } from './video-share.component' 9import { VideoShareComponent } from './video-share.component'
10import { VideoMagnetComponent } from './video-magnet.component' 10import { VideoDownloadComponent } from './video-download.component'
11 11
12@NgModule({ 12@NgModule({
13 imports: [ 13 imports: [
@@ -18,7 +18,7 @@ import { VideoMagnetComponent } from './video-magnet.component'
18 declarations: [ 18 declarations: [
19 VideoWatchComponent, 19 VideoWatchComponent,
20 20
21 VideoMagnetComponent, 21 VideoDownloadComponent,
22 VideoShareComponent, 22 VideoShareComponent,
23 VideoReportComponent 23 VideoReportComponent
24 ], 24 ],
diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts
index 7cf3ea6cc..19490baf2 100644
--- a/client/src/assets/player/peertube-videojs-plugin.ts
+++ b/client/src/assets/player/peertube-videojs-plugin.ts
@@ -158,7 +158,12 @@ const peertubePlugin = function (options: PeertubePluginOptions) {
158 }) 158 })
159 159
160 player.torrent.on('error', err => handleError(err)) 160 player.torrent.on('error', err => handleError(err))
161 player.torrent.on('warning', err => handleError(err)) 161 player.torrent.on('warning', err => {
162 // We don't support HTTP tracker but we don't care -> we use the web socket tracker
163 if (err.message.indexOf('Unsupported tracker protocol: http://') !== -1) return
164
165 return handleError(err)
166 })
162 167
163 player.trigger('videoFileUpdate') 168 player.trigger('videoFileUpdate')
164 169
diff --git a/server.ts b/server.ts
index 72bb11e74..f50e5bad4 100644
--- a/server.ts
+++ b/server.ts
@@ -79,26 +79,6 @@ app.use(morgan('combined', {
79app.use(bodyParser.json({ limit: '500kb' })) 79app.use(bodyParser.json({ limit: '500kb' }))
80app.use(bodyParser.urlencoded({ extended: false })) 80app.use(bodyParser.urlencoded({ extended: false }))
81 81
82// ----------- Views, routes and static files -----------
83
84// API
85const apiRoute = '/api/' + API_VERSION
86app.use(apiRoute, apiRouter)
87
88// Services (oembed...)
89app.use('/services', servicesRouter)
90
91// Client files
92app.use('/', clientsRouter)
93
94// Static files
95app.use('/', staticRouter)
96
97// Always serve index client page (the client is a single page application, let it handle routing)
98app.use('/*', function (req, res, next) {
99 res.sendFile(path.join(__dirname, '../client/dist/index.html'))
100})
101
102// ----------- Tracker ----------- 82// ----------- Tracker -----------
103 83
104const trackerServer = new TrackerServer({ 84const trackerServer = new TrackerServer({
@@ -122,6 +102,30 @@ wss.on('connection', function (ws) {
122 trackerServer.onWebSocketConnection(ws) 102 trackerServer.onWebSocketConnection(ws)
123}) 103})
124 104
105const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer)
106app.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' }))
107app.get('/tracker/scrape', (req, res) => onHttpRequest(req, res, { action: 'scrape' }))
108
109// ----------- Views, routes and static files -----------
110
111// API
112const apiRoute = '/api/' + API_VERSION
113app.use(apiRoute, apiRouter)
114
115// Services (oembed...)
116app.use('/services', servicesRouter)
117
118// Client files
119app.use('/', clientsRouter)
120
121// Static files
122app.use('/', staticRouter)
123
124// Always serve index client page (the client is a single page application, let it handle routing)
125app.use('/*', function (req, res) {
126 res.sendFile(path.join(__dirname, '../client/dist/index.html'))
127})
128
125// ----------- Errors ----------- 129// ----------- Errors -----------
126 130
127// Catch 404 and forward to error handler 131// Catch 404 and forward to error handler
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
index 1402df26a..86ce84dd9 100644
--- a/server/models/video/video-interface.ts
+++ b/server/models/video/video-interface.ts
@@ -18,7 +18,6 @@ export namespace VideoMethods {
18 export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo 18 export type ToFormattedJSON = (this: VideoInstance) => FormattedVideo
19 19
20 export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance 20 export type GetOriginalFile = (this: VideoInstance) => VideoFileInstance
21 export type GenerateMagnetUri = (this: VideoInstance, videoFile: VideoFileInstance) => string
22 export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string 21 export type GetTorrentFileName = (this: VideoInstance, videoFile: VideoFileInstance) => string
23 export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string 22 export type GetVideoFilename = (this: VideoInstance, videoFile: VideoFileInstance) => string
24 export type CreatePreview = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string> 23 export type CreatePreview = (this: VideoInstance, videoFile: VideoFileInstance) => Promise<string>
@@ -108,7 +107,6 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
108 createThumbnail: VideoMethods.CreateThumbnail 107 createThumbnail: VideoMethods.CreateThumbnail
109 createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash 108 createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
110 getOriginalFile: VideoMethods.GetOriginalFile 109 getOriginalFile: VideoMethods.GetOriginalFile
111 generateMagnetUri: VideoMethods.GenerateMagnetUri
112 getPreviewName: VideoMethods.GetPreviewName 110 getPreviewName: VideoMethods.GetPreviewName
113 getPreviewPath: VideoMethods.GetPreviewPath 111 getPreviewPath: VideoMethods.GetPreviewPath
114 getThumbnailName: VideoMethods.GetThumbnailName 112 getThumbnailName: VideoMethods.GetThumbnailName
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 4bd8eb98f..0b1af4d21 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -52,7 +52,6 @@ import { PREVIEWS_SIZE } from '../../initializers/constants'
52 52
53let Video: Sequelize.Model<VideoInstance, VideoAttributes> 53let Video: Sequelize.Model<VideoInstance, VideoAttributes>
54let getOriginalFile: VideoMethods.GetOriginalFile 54let getOriginalFile: VideoMethods.GetOriginalFile
55let generateMagnetUri: VideoMethods.GenerateMagnetUri
56let getVideoFilename: VideoMethods.GetVideoFilename 55let getVideoFilename: VideoMethods.GetVideoFilename
57let getThumbnailName: VideoMethods.GetThumbnailName 56let getThumbnailName: VideoMethods.GetThumbnailName
58let getThumbnailPath: VideoMethods.GetThumbnailPath 57let getThumbnailPath: VideoMethods.GetThumbnailPath
@@ -254,7 +253,6 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
254 createPreview, 253 createPreview,
255 createThumbnail, 254 createThumbnail,
256 createTorrentAndSetInfoHash, 255 createTorrentAndSetInfoHash,
257 generateMagnetUri,
258 getPreviewName, 256 getPreviewName,
259 getPreviewPath, 257 getPreviewPath,
260 getThumbnailName, 258 getThumbnailName,
@@ -426,33 +424,6 @@ createTorrentAndSetInfoHash = function (this: VideoInstance, videoFile: VideoFil
426 }) 424 })
427} 425}
428 426
429generateMagnetUri = function (this: VideoInstance, videoFile: VideoFileInstance) {
430 let baseUrlHttp
431 let baseUrlWs
432
433 if (this.isOwned()) {
434 baseUrlHttp = CONFIG.WEBSERVER.URL
435 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
436 } else {
437 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
438 baseUrlWs = REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
439 }
440
441 const xs = baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
442 const announce = [ baseUrlWs + '/tracker/socket' ]
443 const urlList = [ baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) ]
444
445 const magnetHash = {
446 xs,
447 announce,
448 urlList,
449 infoHash: videoFile.infoHash,
450 name: this.name
451 }
452
453 return magnetUtil.encode(magnetHash)
454}
455
456getEmbedPath = function (this: VideoInstance) { 427getEmbedPath = function (this: VideoInstance) {
457 return '/videos/embed/' + this.uuid 428 return '/videos/embed/' + this.uuid
458} 429}
@@ -516,6 +487,7 @@ toFormattedJSON = function (this: VideoInstance) {
516 } 487 }
517 488
518 // Format and sort video files 489 // Format and sort video files
490 const { baseUrlHttp, baseUrlWs } = getBaseUrls(this)
519 json.files = this.VideoFiles 491 json.files = this.VideoFiles
520 .map(videoFile => { 492 .map(videoFile => {
521 let resolutionLabel = videoFile.resolution + 'p' 493 let resolutionLabel = videoFile.resolution + 'p'
@@ -523,8 +495,10 @@ toFormattedJSON = function (this: VideoInstance) {
523 const videoFileJson = { 495 const videoFileJson = {
524 resolution: videoFile.resolution, 496 resolution: videoFile.resolution,
525 resolutionLabel, 497 resolutionLabel,
526 magnetUri: this.generateMagnetUri(videoFile), 498 magnetUri: generateMagnetUri(this, videoFile, baseUrlHttp, baseUrlWs),
527 size: videoFile.size 499 size: videoFile.size,
500 torrentUrl: getTorrentUrl(this, videoFile, baseUrlHttp),
501 fileUrl: getVideoFileUrl(this, videoFile, baseUrlHttp)
528 } 502 }
529 503
530 return videoFileJson 504 return videoFileJson
@@ -972,3 +946,42 @@ function createBaseVideosWhere () {
972 } 946 }
973 } 947 }
974} 948}
949
950function getBaseUrls (video: VideoInstance) {
951 let baseUrlHttp
952 let baseUrlWs
953
954 if (video.isOwned()) {
955 baseUrlHttp = CONFIG.WEBSERVER.URL
956 baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
957 } else {
958 baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host
959 baseUrlWs = REMOTE_SCHEME.WS + '://' + video.Author.Pod.host
960 }
961
962 return { baseUrlHttp, baseUrlWs }
963}
964
965function getTorrentUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) {
966 return baseUrlHttp + STATIC_PATHS.TORRENTS + video.getTorrentFileName(videoFile)
967}
968
969function getVideoFileUrl (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string) {
970 return baseUrlHttp + STATIC_PATHS.WEBSEED + video.getVideoFilename(videoFile)
971}
972
973function generateMagnetUri (video: VideoInstance, videoFile: VideoFileInstance, baseUrlHttp: string, baseUrlWs: string) {
974 const xs = getTorrentUrl(video, videoFile, baseUrlHttp)
975 const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
976 const urlList = [ getVideoFileUrl(video, videoFile, baseUrlHttp) ]
977
978 const magnetHash = {
979 xs,
980 announce,
981 urlList,
982 infoHash: videoFile.infoHash,
983 name: video.name
984 }
985
986 return magnetUtil.encode(magnetHash)
987}
diff --git a/server/tests/api/multiple-pods.ts b/server/tests/api/multiple-pods.ts
index 6c11aace5..e0ccb3058 100644
--- a/server/tests/api/multiple-pods.ts
+++ b/server/tests/api/multiple-pods.ts
@@ -106,6 +106,8 @@ describe('Test multiple pods', function () {
106 const file = video.files[0] 106 const file = video.files[0]
107 const magnetUri = file.magnetUri 107 const magnetUri = file.magnetUri
108 expect(file.magnetUri).to.have.lengthOf.above(2) 108 expect(file.magnetUri).to.have.lengthOf.above(2)
109 expect(file.torrentUrl).to.equal(`http://${video.podHost}/static/torrents/${video.uuid}-${file.resolution}.torrent`)
110 expect(file.fileUrl).to.equal(`http://${video.podHost}/static/webseed/${video.uuid}-${file.resolution}.webm`)
109 expect(file.resolution).to.equal(720) 111 expect(file.resolution).to.equal(720)
110 expect(file.resolutionLabel).to.equal('720p') 112 expect(file.resolutionLabel).to.equal('720p')
111 expect(file.size).to.equal(572456) 113 expect(file.size).to.equal(572456)
diff --git a/server/tests/api/single-pod.ts b/server/tests/api/single-pod.ts
index 82bc51a3e..71017b2b3 100644
--- a/server/tests/api/single-pod.ts
+++ b/server/tests/api/single-pod.ts
@@ -127,6 +127,8 @@ describe('Test a single pod', function () {
127 const file = video.files[0] 127 const file = video.files[0]
128 const magnetUri = file.magnetUri 128 const magnetUri = file.magnetUri
129 expect(file.magnetUri).to.have.lengthOf.above(2) 129 expect(file.magnetUri).to.have.lengthOf.above(2)
130 expect(file.torrentUrl).to.equal(`${server.url}/static/torrents/${video.uuid}-${file.resolution}.torrent`)
131 expect(file.fileUrl).to.equal(`${server.url}/static/webseed/${video.uuid}-${file.resolution}.webm`)
130 expect(file.resolution).to.equal(720) 132 expect(file.resolution).to.equal(720)
131 expect(file.resolutionLabel).to.equal('720p') 133 expect(file.resolutionLabel).to.equal('720p')
132 expect(file.size).to.equal(218910) 134 expect(file.size).to.equal(218910)
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index bbcada845..8e47ac069 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -3,6 +3,8 @@ export interface VideoFile {
3 resolution: number 3 resolution: number
4 resolutionLabel: string 4 resolutionLabel: string
5 size: number // Bytes 5 size: number // Bytes
6 torrentUrl: string
7 fileUrl: string
6} 8}
7 9
8export interface Video { 10export interface Video {