Compare commits
	
		
			1 Commits
		
	
	
		
			master
			...
			hls_on_the
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5b8a63c0aa | 
| @@ -3,12 +3,15 @@ package api | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"openmediacenter/apiGo/api/api" | ||||
| 	"openmediacenter/apiGo/api/types" | ||||
| 	"openmediacenter/apiGo/config" | ||||
| 	"openmediacenter/apiGo/database" | ||||
| 	"openmediacenter/apiGo/videoparser/hls" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| ) | ||||
| @@ -398,6 +401,62 @@ func loadVideosHandlers() { | ||||
|  | ||||
| 		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() { | ||||
|   | ||||
| @@ -24,6 +24,7 @@ type FeaturesT struct { | ||||
| type GeneralT struct { | ||||
| 	VerboseLogging bool | ||||
| 	ReindexPrefix  string | ||||
| 	TmpDir         string | ||||
| } | ||||
|  | ||||
| type FileConfT struct { | ||||
| @@ -44,6 +45,7 @@ func defaultConfig() *FileConfT { | ||||
| 		General: GeneralT{ | ||||
| 			VerboseLogging: false, | ||||
| 			ReindexPrefix:  "/var/www/openmediacenter", | ||||
| 			TmpDir:         "/tmp/openmediacenter", | ||||
| 		}, | ||||
| 		Features: FeaturesT{ | ||||
| 			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/react-fontawesome": "^0.1.13", | ||||
|     "bootstrap": "^5.0.2", | ||||
|     "hls.js": "^1.2.9", | ||||
|     "plyr-react": "^3.0.7", | ||||
|     "react": "^17.0.1", | ||||
|     "react-bootstrap": "^1.4.0", | ||||
| @@ -22,7 +23,7 @@ | ||||
|     "typescript": "^4.3.5" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "react-scripts start", | ||||
|     "start": "react-scripts --openssl-legacy-provider start", | ||||
|     "build": "CI=false react-scripts build", | ||||
|     "test": "CI=true react-scripts test --reporters=jest-junit --verbose --silent --coverage --reporters=default", | ||||
|     "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 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 Tag from '../../elements/Tag/Tag'; | ||||
| import AddTagPopup from '../../elements/Popups/AddTagPopup/AddTagPopup'; | ||||
| @@ -15,7 +13,7 @@ import ActorTile from '../../elements/ActorTile/ActorTile'; | ||||
| import {withRouter} from 'react-router-dom'; | ||||
| import {APINode, callAPI} from '../../utils/Api'; | ||||
| import {RouteComponentProps} from 'react-router'; | ||||
| import {DefaultPlyrOptions, GeneralSuccess} from '../../types/GeneralTypes'; | ||||
| import {GeneralSuccess} from '../../types/GeneralTypes'; | ||||
| import {ActorType, TagType} from '../../types/VideoTypes'; | ||||
| import PlyrJS from 'plyr'; | ||||
| import {IconButton} from '../../elements/GPElements/Button'; | ||||
| @@ -23,6 +21,7 @@ import {VideoTypes} from '../../types/ApiTypes'; | ||||
| import GlobalInfos from '../../utils/GlobalInfos'; | ||||
| import {ButtonPopup} from '../../elements/Popups/ButtonPopup/ButtonPopup'; | ||||
| import {FeatureContext} from '../../utils/context/FeatureContext'; | ||||
| import HLSPlayer from './HLSPlayer'; | ||||
|  | ||||
| interface Props extends RouteComponentProps<{id: string}> {} | ||||
|  | ||||
| @@ -84,11 +83,8 @@ export class Player extends React.Component<Props, mystate> { | ||||
|  | ||||
|                 <div className={style.videowrapper}> | ||||
|                     {/* video component is added here */} | ||||
|                     {this.state.sources ? ( | ||||
|                         <Plyr style={plyrstyle} source={this.state.sources} options={DefaultPlyrOptions} /> | ||||
|                     ) : ( | ||||
|                         <div>not loaded yet</div> | ||||
|                     )} | ||||
|                     {/*<Plyr style={plyrstyle} source={this.state.sources} options={DefaultPlyrOptions} />*/} | ||||
|                     {this.state.sources ? <HLSPlayer videoid={this.state.movieId} /> : <div>not loaded yet</div>} | ||||
|                     <div className={style.videoactions}> | ||||
|                         <IconButton icon={faThumbsUp} onClick={(): void => this.likebtn()} title='Like!' /> | ||||
|                         <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" | ||||
|     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: | ||||
|   version "1.0.1" | ||||
|   resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user