using System; using System.Collections.Generic; using System.Reactive.Linq; using System.Threading.Tasks; using System.Threading; using ReactNative; using ReactNative.Bridge; using JuvoPlayer; using JuvoPlayer.Common; using JuvoLogger; using ILogger = JuvoLogger.ILogger; using ElmSharp; using ReactNative.Modules.Core; using Newtonsoft.Json.Linq; namespace RNJuvoPlayerApi { /// /// A module that allows JS to share data. /// public class RNJuvoPlayerApiModule : ReactContextNativeModuleBase, ILifecycleEventListener, ISeekLogicClient { /// /// Instantiates the . /// /// private Timer playbackTimer; private SeekLogic seekLogic = null; // needs to be initialized in the constructor! private ILogger Logger = LoggerManager.GetInstance().GetLogger("JuvoRN"); public readonly string Tag = "JuvoRN"; EcoreEvent _keyDown; EcoreEvent _keyUp; Window window = ReactProgram.RctWindow; //The main window of the application has to be transparent. List[] allStreamsDescriptions = { null, null, null }; public IPlayerService Player { get; private set; } public RNJuvoPlayerApiModule(ReactContext reactContext) : base(reactContext) { seekLogic = new SeekLogic(this); } private void InitializeJuvoPlayer() { Player = new PlayerServiceProxy(new PlayerServiceImpl(window)); Player.StateChanged() .Subscribe(OnPlayerStateChanged, OnPlaybackCompleted); Player.PlaybackError() .Subscribe(message => { var param = new JObject(); param.Add("Message", message); SendEvent("onPlaybackError", param); }); Player.BufferingProgress() .Subscribe(UpdateBufferingProgress); } /// /// The name of the native module. /// public override string Name { get { return "RNJuvoPlayerApi"; } } private void SendEvent(string eventName, JObject parameters) { Context.GetJavaScriptModule() .emit(eventName, parameters); } public override void Initialize() { Context.AddLifecycleEventListener(this); _keyDown = new EcoreEvent(EcoreEventType.KeyDown, EcoreKeyEventArgs.Create); _keyDown.On += (s, e) => { //Propagate the key press event to JavaScript module var param = new JObject(); param.Add("KeyName", e.KeyName); param.Add("KeyCode", e.KeyCode); SendEvent("onTVKeyDown", param); }; _keyUp = new EcoreEvent(EcoreEventType.KeyUp, EcoreKeyEventArgs.Create); _keyUp.On += (s, e) => { //Propagate the key press event to JavaScript module var param = new JObject(); param.Add("KeyName", e.KeyName); param.Add("KeyCode", e.KeyCode); SendEvent("onTVKeyUp", param); }; } private void OnPlayerStateChanged(PlayerState state) { string value = "Idle"; int interval = 100; switch (state) { case PlayerState.Prepared: Player.Start(); playbackTimer = new Timer( callback: new TimerCallback(UpdatePlayTime), state: seekLogic.CurrentPositionUI, dueTime: 0, period: interval); value = "Prepared"; break; case PlayerState.Playing: value = "Playing"; playbackTimer.Change(0, interval); //resume progress info update break; case PlayerState.Paused: value = "Paused"; playbackTimer.Change(Timeout.Infinite, Timeout.Infinite); //suspend progress info update break; case PlayerState.Idle: playbackTimer?.Dispose(); break; } var param = new JObject(); param.Add("State", value); SendEvent("onPlayerStateChanged", param); } private void OnPlaybackCompleted() { var param = new JObject(); param.Add("State", "Completed"); SendEvent("onPlaybackCompleted", param); } public void OnDestroy() { Logger?.Info("Destroying JuvoPlayerModule..."); } public void OnResume() { } public void OnSuspend() { } private void UpdateBufferingProgress(int percent) { //Propagate the bufffering progress event to JavaScript module var param = new JObject(); param.Add("Percent", (int)percent); SendEvent("onUpdateBufferingProgress", param); } private void UpdatePlayTime(object timerState) { string txt = ""; if (Player?.CurrentCueText != null) { txt = Player?.CurrentCueText; } var param = new JObject(); param.Add("Total", (int)seekLogic.Duration.TotalMilliseconds); param.Add("Current", (int)seekLogic.CurrentPositionUI.TotalMilliseconds); param.Add("SubtiteText", txt); SendEvent("onUpdatePlayTime", param); } private void Play(string videoURI, List drmDataList, string streamingProtocol) { try { if (videoURI == null) return; if (Player?.State == PlayerState.Playing) return; InitializeJuvoPlayer(); Player.SetSource(new ClipDefinition { Type = streamingProtocol, Url = videoURI, Subtitles = new List(), DRMDatas = drmDataList }); } catch (Exception e) { Logger?.Error(Tag, e.Message); } } public Task Seek(TimeSpan to) { var param = new JObject(); param.Add("To", (int)to.TotalMilliseconds); SendEvent("onSeek", param); return Player?.SeekTo(to); } //////////////////JS methods////////////////// [ReactMethod] public void GetStreamsDescription(int StreamTypeIndex) { var index = (JuvoPlayer.Common.StreamType)StreamTypeIndex; if (index == JuvoPlayer.Common.StreamType.Subtitle) { this.allStreamsDescriptions[StreamTypeIndex] = new List { new StreamDescription { Default = true, Description = "off", Id = 0, StreamType = (StreamType)StreamTypeIndex } }; this.allStreamsDescriptions[StreamTypeIndex].AddRange(Player.GetStreamsDescription((StreamType)StreamTypeIndex)); } else { this.allStreamsDescriptions[StreamTypeIndex] = Player.GetStreamsDescription((StreamType)StreamTypeIndex); } var param = new JObject(); param.Add("Description", Newtonsoft.Json.JsonConvert.SerializeObject(this.allStreamsDescriptions[StreamTypeIndex])); param.Add("StreamTypeIndex", (int)StreamTypeIndex); SendEvent("onGotStreamsDescription", param); } [ReactMethod] public void SetStream(int SelectedIndex, int StreamTypeIndex) { if (this.allStreamsDescriptions[StreamTypeIndex] == null) return; if (SelectedIndex != -1) { var index = (JuvoPlayer.Common.StreamType)StreamTypeIndex; if (index == JuvoPlayer.Common.StreamType.Subtitle) { if (SelectedIndex == 0) { Player.DeactivateStream(StreamType.Subtitle); return; } } var stream = (StreamDescription)this.allStreamsDescriptions[StreamTypeIndex][SelectedIndex]; Player.ChangeActiveStream(stream); } } [ReactMethod] public void Log(string message) { Logger?.Info(message); } [ReactMethod] public void StartPlayback(string videoURI, string drmDatasJSON, string streamingProtocol) { try { var drmDataList = (drmDatasJSON != null) ? JuvoPlayer.Utils.JSONFileReader.DeserializeJsonText>(drmDatasJSON) : new List(); Play(videoURI, drmDataList, streamingProtocol); } catch (Exception e) { Logger?.Error(Tag, "StartPlayback failed... " + e.Message); } finally { seekLogic.Reset(); } } [ReactMethod] public void StopPlayback() { Player?.Stop(); Player?.Dispose(); Player = null; seekLogic.IsSeekAccumulationInProgress = false; } [ReactMethod] public void PauseResumePlayback() { switch (Player.State) { case JuvoPlayer.Common.PlayerState.Playing: Player?.Pause(); break; case JuvoPlayer.Common.PlayerState.Paused: Player?.Start(); break; } } [ReactMethod] public void Forward() { seekLogic.SeekForward(); } [ReactMethod] public void Rewind() { seekLogic.SeekBackward(); } } }