Compare commits
	
		
			1 Commits
		
	
	
		
			master
			...
			hls_on_the
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5b8a63c0aa | 
| @@ -3,12 +3,15 @@ package api | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"openmediacenter/apiGo/api/api" | 	"openmediacenter/apiGo/api/api" | ||||||
| 	"openmediacenter/apiGo/api/types" | 	"openmediacenter/apiGo/api/types" | ||||||
| 	"openmediacenter/apiGo/config" | 	"openmediacenter/apiGo/config" | ||||||
| 	"openmediacenter/apiGo/database" | 	"openmediacenter/apiGo/database" | ||||||
|  | 	"openmediacenter/apiGo/videoparser/hls" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"os/exec" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| @@ -398,6 +401,62 @@ func loadVideosHandlers() { | |||||||
|  |  | ||||||
| 		context.Json(result) | 		context.Json(result) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
|  | 	api.AddHandler("loadM3U8", api.VideoNode, api.PermUnauthorized, func(ctx api.Context) { | ||||||
|  | 		param := ctx.GetRequest().URL.Query().Get("id") | ||||||
|  | 		id, _ := strconv.Atoi(param) | ||||||
|  |  | ||||||
|  | 		mylist := | ||||||
|  | 			`#EXTM3U | ||||||
|  | #EXT-X-VERSION:4 | ||||||
|  | #EXT-X-MEDIA-SEQUENCE:0 | ||||||
|  | #EXT-X-ALLOW-CACHE:NO | ||||||
|  | #EXT-X-TARGETDURATION:10 | ||||||
|  | #EXT-X-START:TIME-OFFSET=0 | ||||||
|  | #EXT-X-PLAYLIST-TYPE:VOD | ||||||
|  | ` | ||||||
|  | 		// ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 input.mp4 | ||||||
|  | 		cmd := exec.Command("ffprobe", | ||||||
|  | 			"-v", "error", | ||||||
|  | 			"-show_entries", "format=duration", | ||||||
|  | 			"-of", "default=noprint_wrappers=1:nokey=1", | ||||||
|  | 			hls.GetVideoPathById(uint32(id))) | ||||||
|  | 		stdout, err := cmd.Output() | ||||||
|  | 		// | ||||||
|  | 		if err != nil { | ||||||
|  | 			fmt.Println(err.Error()) | ||||||
|  | 			fmt.Println(string(err.(*exec.ExitError).Stderr)) | ||||||
|  | 		} | ||||||
|  | 		secss, _, _ := strings.Cut(string(stdout), ".") | ||||||
|  | 		secsi, err := strconv.Atoi(secss) | ||||||
|  |  | ||||||
|  | 		i := 0 | ||||||
|  | 		for ; i < secsi/10; i++ { | ||||||
|  | 			mylist += fmt.Sprintf( | ||||||
|  | 				`#EXTINF:10.0, | ||||||
|  | /api/video/getVideoSegment?id=%d&idx=%d | ||||||
|  | `, id, i) | ||||||
|  | 		} | ||||||
|  | 		mylist += fmt.Sprintf( | ||||||
|  | 			`#EXTINF:%s, | ||||||
|  | /api/video/getVideoSegment?id=%d&idx=%d | ||||||
|  | EXT-X-ENDLIST | ||||||
|  | `, fmt.Sprintf("%d.0", secsi%10), id, i) | ||||||
|  | 		ctx.Text(mylist) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	api.AddHandler("getVideoSegment", api.VideoNode, api.PermUnauthorized, func(ctx api.Context) { | ||||||
|  | 		params := ctx.GetRequest().URL.Query() | ||||||
|  | 		idxs := params.Get("idx") | ||||||
|  | 		ids := params.Get("id") | ||||||
|  |  | ||||||
|  | 		id, _ := strconv.Atoi(ids) | ||||||
|  | 		idx, _ := strconv.Atoi(idxs) | ||||||
|  | 		// todo error handling | ||||||
|  |  | ||||||
|  | 		tmppath := hls.GetSegment(uint32(idx), uint32(id)) | ||||||
|  | 		http.ServeFile(ctx.GetWriter(), ctx.GetRequest(), tmppath) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| func addToVideoHandlers() { | func addToVideoHandlers() { | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ type FeaturesT struct { | |||||||
| type GeneralT struct { | type GeneralT struct { | ||||||
| 	VerboseLogging bool | 	VerboseLogging bool | ||||||
| 	ReindexPrefix  string | 	ReindexPrefix  string | ||||||
|  | 	TmpDir         string | ||||||
| } | } | ||||||
|  |  | ||||||
| type FileConfT struct { | type FileConfT struct { | ||||||
| @@ -44,6 +45,7 @@ func defaultConfig() *FileConfT { | |||||||
| 		General: GeneralT{ | 		General: GeneralT{ | ||||||
| 			VerboseLogging: false, | 			VerboseLogging: false, | ||||||
| 			ReindexPrefix:  "/var/www/openmediacenter", | 			ReindexPrefix:  "/var/www/openmediacenter", | ||||||
|  | 			TmpDir:         "/tmp/openmediacenter", | ||||||
| 		}, | 		}, | ||||||
| 		Features: FeaturesT{ | 		Features: FeaturesT{ | ||||||
| 			DisableTVSupport:     false, | 			DisableTVSupport:     false, | ||||||
|   | |||||||
							
								
								
									
										133
									
								
								apiGo/videoparser/hls/HlsDecoding.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								apiGo/videoparser/hls/HlsDecoding.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | |||||||
|  | package hls | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"openmediacenter/apiGo/config" | ||||||
|  | 	"openmediacenter/apiGo/database" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | type TranscodingState struct { | ||||||
|  | 	Finished bool | ||||||
|  | 	Active   bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var transcodeAcive = make(map[uint32]TranscodingState) | ||||||
|  |  | ||||||
|  | func startSegmentation(videoid uint32) { | ||||||
|  | 	transcodeAcive[videoid] = TranscodingState{ | ||||||
|  | 		Finished: false, | ||||||
|  | 		Active:   true, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cfg := config.GetConfig() | ||||||
|  | 	outpath := fmt.Sprintf("%s/%d", cfg.General.TmpDir, videoid) | ||||||
|  |  | ||||||
|  | 	if !fileExists(outpath) { | ||||||
|  | 		err := os.MkdirAll(outpath, os.ModePerm) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Println(err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	app := "ffmpeg" | ||||||
|  | 	inputpath := GetVideoPathById(videoid) | ||||||
|  | 	fmt.Println(inputpath) | ||||||
|  | 	cmd := exec.Command(app, | ||||||
|  | 		//"-hide_banner", | ||||||
|  | 		//"-loglevel", "panic", | ||||||
|  | 		"-n", | ||||||
|  | 		//"-t", "10.0", | ||||||
|  | 		//"-ss", fmt.Sprintf("%d", id*10), | ||||||
|  | 		"-i", inputpath, | ||||||
|  | 		//"-g", "52", | ||||||
|  | 		//"-sc_threshold", "0", | ||||||
|  | 		//"-force_key_frames", "expr:gte(t,n_forced*10)", | ||||||
|  | 		//"-strict", "experimental", | ||||||
|  | 		//"-movflags", "+frag_keyframe+separate_moof+omit_tfhd_offset+empty_moov", | ||||||
|  | 		//"-c:v", "libx264", | ||||||
|  | 		//"-crf", "18", | ||||||
|  | 		// | ||||||
|  | 		//"-f", "segment", | ||||||
|  | 		//"-segment_time_delta", "0.2", | ||||||
|  | 		//"-segment_format", "mpegts", | ||||||
|  | 		//"-segment_times", commaSeparatedTimes, | ||||||
|  | 		//"-segment_start_number", `0`, | ||||||
|  | 		//"-segment_list_type", "flat", | ||||||
|  | 		//"-segment_list", | ||||||
|  | 		"-preset", "veryfast", | ||||||
|  | 		//"-maxrate", "4000k", | ||||||
|  | 		//"-bufsize", "8000k", | ||||||
|  | 		//"-vf", "scale=1280:-1,format=yuv420p", | ||||||
|  | 		//"-c:a", "aac", | ||||||
|  | 		"-force_key_frames", "expr:gte(t,n_forced*10)", | ||||||
|  | 		"-strict", "-2", | ||||||
|  | 		"-c:a", "aac", | ||||||
|  | 		"-c:v", "libx264", | ||||||
|  | 		"-f", "segment", | ||||||
|  | 		"-segment_list_type", "m3u8", | ||||||
|  | 		"-segment_time", "10.0", | ||||||
|  | 		"-segment_time_delta", "0.001", | ||||||
|  | 		"-segment_list", "test.m3u8", outpath+"/part%02d.ts") | ||||||
|  |  | ||||||
|  | 	stdout, err := cmd.Output() | ||||||
|  | 	// | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println(err.Error()) | ||||||
|  | 		fmt.Println(string(err.(*exec.ExitError).Stderr)) | ||||||
|  | 	} | ||||||
|  | 	fmt.Println(stdout) | ||||||
|  | 	fmt.Println("finished transcoding") | ||||||
|  |  | ||||||
|  | 	transcodeAcive[videoid] = TranscodingState{ | ||||||
|  | 		Finished: true, | ||||||
|  | 		Active:   false, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func fileExists(path string) bool { | ||||||
|  | 	_, err := os.Stat(path) | ||||||
|  | 	return !os.IsNotExist(err) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetVideoPathById(videoid uint32) string { | ||||||
|  | 	query := fmt.Sprintf(`SELECT movie_url FROM videos WHERE movie_id=%d`, videoid) | ||||||
|  | 	var url string | ||||||
|  |  | ||||||
|  | 	err := database.QueryRow(query).Scan(&url) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	mSettings, _, _ := database.GetSettings() | ||||||
|  | 	vidFolder := config.GetConfig().General.ReindexPrefix + mSettings.VideoPath | ||||||
|  | 	return vidFolder + url | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func GetSegment(segIdx uint32, videoid uint32) string { | ||||||
|  | 	cfg := config.GetConfig() | ||||||
|  |  | ||||||
|  | 	i, ok := transcodeAcive[videoid] | ||||||
|  | 	if ok { | ||||||
|  | 		if !i.Active && !i.Finished { | ||||||
|  | 			go startSegmentation(videoid) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		go startSegmentation(videoid) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// todo timeout | ||||||
|  | 	tspath := fmt.Sprintf("%s/%d/part%02d.ts", cfg.General.TmpDir, videoid, segIdx) | ||||||
|  | 	if ok && i.Finished == true { | ||||||
|  | 		return tspath | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fmt.Println("checking if part exists") | ||||||
|  | 	for !fileExists(fmt.Sprintf("%s/%d/part%02d.ts", cfg.General.TmpDir, videoid, segIdx+1)) { | ||||||
|  | 		time.Sleep(100 * time.Millisecond) | ||||||
|  | 	} | ||||||
|  | 	return tspath | ||||||
|  | } | ||||||
| @@ -13,6 +13,7 @@ | |||||||
|     "@fortawesome/free-solid-svg-icons": "^5.15.1", |     "@fortawesome/free-solid-svg-icons": "^5.15.1", | ||||||
|     "@fortawesome/react-fontawesome": "^0.1.13", |     "@fortawesome/react-fontawesome": "^0.1.13", | ||||||
|     "bootstrap": "^5.0.2", |     "bootstrap": "^5.0.2", | ||||||
|  |     "hls.js": "^1.2.9", | ||||||
|     "plyr-react": "^3.0.7", |     "plyr-react": "^3.0.7", | ||||||
|     "react": "^17.0.1", |     "react": "^17.0.1", | ||||||
|     "react-bootstrap": "^1.4.0", |     "react-bootstrap": "^1.4.0", | ||||||
| @@ -22,7 +23,7 @@ | |||||||
|     "typescript": "^4.3.5" |     "typescript": "^4.3.5" | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "react-scripts start", |     "start": "react-scripts --openssl-legacy-provider start", | ||||||
|     "build": "CI=false react-scripts build", |     "build": "CI=false react-scripts build", | ||||||
|     "test": "CI=true react-scripts test --reporters=jest-junit --verbose --silent --coverage --reporters=default", |     "test": "CI=true react-scripts test --reporters=jest-junit --verbose --silent --coverage --reporters=default", | ||||||
|     "lint": "eslint --format gitlab src/", |     "lint": "eslint --format gitlab src/", | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								src/pages/Player/HLSPlayer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/pages/Player/HLSPlayer.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import React, {useEffect, useRef} from 'react'; | ||||||
|  | import Hls from 'hls.js'; | ||||||
|  | import {PlyrInstance, PlyrProps, Plyr} from 'plyr-react'; | ||||||
|  | import plyrstyle from 'plyr-react/dist/plyr.css'; | ||||||
|  | import {DefaultPlyrOptions} from '../../types/GeneralTypes'; | ||||||
|  |  | ||||||
|  | interface Props { | ||||||
|  |     // children?: JSX.Element; | ||||||
|  |     videoid: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const HLSPlayer = (props: Props): JSX.Element => { | ||||||
|  |     const ref = useRef(null); | ||||||
|  |     useEffect(() => { | ||||||
|  |         const loadVideo = async (): Promise<void> => { | ||||||
|  |             const video = document.getElementById('plyr') as HTMLVideoElement; | ||||||
|  |             const hls = new Hls(); | ||||||
|  |             hls.loadSource('/api/video/loadM3U8?id=' + props.videoid); | ||||||
|  |             hls.attachMedia(video); | ||||||
|  |             // @ts-ignore | ||||||
|  |             ref.current!.plyr.media = video; | ||||||
|  |  | ||||||
|  |             hls.on(Hls.Events.MANIFEST_PARSED, function () { | ||||||
|  |                 // @ts-ignore | ||||||
|  |                 (ref.current!.plyr as PlyrInstance).play(); | ||||||
|  |             }); | ||||||
|  |         }; | ||||||
|  |         loadVideo(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return <Plyr style={plyrstyle} options={DefaultPlyrOptions} id='plyr' source={{} as PlyrProps['source']} ref={ref} />; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default HLSPlayer; | ||||||
| @@ -1,9 +1,7 @@ | |||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  |  | ||||||
| import style from './Player.module.css'; | import style from './Player.module.css'; | ||||||
| import plyrstyle from 'plyr-react/dist/plyr.css'; |  | ||||||
|  |  | ||||||
| import {Plyr} from 'plyr-react'; |  | ||||||
| import SideBar, {SideBarItem, SideBarTitle} from '../../elements/SideBar/SideBar'; | import SideBar, {SideBarItem, SideBarTitle} from '../../elements/SideBar/SideBar'; | ||||||
| import Tag from '../../elements/Tag/Tag'; | import Tag from '../../elements/Tag/Tag'; | ||||||
| import AddTagPopup from '../../elements/Popups/AddTagPopup/AddTagPopup'; | import AddTagPopup from '../../elements/Popups/AddTagPopup/AddTagPopup'; | ||||||
| @@ -15,7 +13,7 @@ import ActorTile from '../../elements/ActorTile/ActorTile'; | |||||||
| import {withRouter} from 'react-router-dom'; | import {withRouter} from 'react-router-dom'; | ||||||
| import {APINode, callAPI} from '../../utils/Api'; | import {APINode, callAPI} from '../../utils/Api'; | ||||||
| import {RouteComponentProps} from 'react-router'; | import {RouteComponentProps} from 'react-router'; | ||||||
| import {DefaultPlyrOptions, GeneralSuccess} from '../../types/GeneralTypes'; | import {GeneralSuccess} from '../../types/GeneralTypes'; | ||||||
| import {ActorType, TagType} from '../../types/VideoTypes'; | import {ActorType, TagType} from '../../types/VideoTypes'; | ||||||
| import PlyrJS from 'plyr'; | import PlyrJS from 'plyr'; | ||||||
| import {IconButton} from '../../elements/GPElements/Button'; | import {IconButton} from '../../elements/GPElements/Button'; | ||||||
| @@ -23,6 +21,7 @@ import {VideoTypes} from '../../types/ApiTypes'; | |||||||
| import GlobalInfos from '../../utils/GlobalInfos'; | import GlobalInfos from '../../utils/GlobalInfos'; | ||||||
| import {ButtonPopup} from '../../elements/Popups/ButtonPopup/ButtonPopup'; | import {ButtonPopup} from '../../elements/Popups/ButtonPopup/ButtonPopup'; | ||||||
| import {FeatureContext} from '../../utils/context/FeatureContext'; | import {FeatureContext} from '../../utils/context/FeatureContext'; | ||||||
|  | import HLSPlayer from './HLSPlayer'; | ||||||
|  |  | ||||||
| interface Props extends RouteComponentProps<{id: string}> {} | interface Props extends RouteComponentProps<{id: string}> {} | ||||||
|  |  | ||||||
| @@ -84,11 +83,8 @@ export class Player extends React.Component<Props, mystate> { | |||||||
|  |  | ||||||
|                 <div className={style.videowrapper}> |                 <div className={style.videowrapper}> | ||||||
|                     {/* video component is added here */} |                     {/* video component is added here */} | ||||||
|                     {this.state.sources ? ( |                     {/*<Plyr style={plyrstyle} source={this.state.sources} options={DefaultPlyrOptions} />*/} | ||||||
|                         <Plyr style={plyrstyle} source={this.state.sources} options={DefaultPlyrOptions} /> |                     {this.state.sources ? <HLSPlayer videoid={this.state.movieId} /> : <div>not loaded yet</div>} | ||||||
|                     ) : ( |  | ||||||
|                         <div>not loaded yet</div> |  | ||||||
|                     )} |  | ||||||
|                     <div className={style.videoactions}> |                     <div className={style.videoactions}> | ||||||
|                         <IconButton icon={faThumbsUp} onClick={(): void => this.likebtn()} title='Like!' /> |                         <IconButton icon={faThumbsUp} onClick={(): void => this.likebtn()} title='Like!' /> | ||||||
|                         <IconButton icon={faTag} onClick={(): void => this.setState({popupvisible: true})} title='Add Tag!' /> |                         <IconButton icon={faTag} onClick={(): void => this.setState({popupvisible: true})} title='Add Tag!' /> | ||||||
|   | |||||||
| @@ -6191,6 +6191,11 @@ history@^4.9.0: | |||||||
|     tiny-warning "^1.0.0" |     tiny-warning "^1.0.0" | ||||||
|     value-equal "^1.0.1" |     value-equal "^1.0.1" | ||||||
|  |  | ||||||
|  | hls.js@^1.2.9: | ||||||
|  |   version "1.2.9" | ||||||
|  |   resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.2.9.tgz#2f25e42ec4c2ea8c88ab23c0f854f39062d45ac9" | ||||||
|  |   integrity sha512-SPjm8ix0xe6cYzwDvdVGh2QvQPDkCYrGWpZu6bRaKNNVyEGWM9uF0pooh/Lqj/g8QBQgPFEx1vHzW8SyMY9rqg== | ||||||
|  |  | ||||||
| hmac-drbg@^1.0.1: | hmac-drbg@^1.0.1: | ||||||
|   version "1.0.1" |   version "1.0.1" | ||||||
|   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" |   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user