From a3d2c43ef09f6f8fa95751f85fd403c6621b3fc6 Mon Sep 17 00:00:00 2001 From: Hamid Reza Mohammadi Date: Fri, 26 Nov 2021 11:16:03 +0330 Subject: [PATCH] #250 use HTML audio instead of jplayer for playing recitations --- GanjooRazor/Pages/Index.cshtml | 1 + GanjooRazor/Pages/Index.cshtml.cs | 8 + GanjooRazor/Pages/_AudioPlayerPartial.cshtml | 176 +++++++ .../Pages/_AudioPlayerPartial.cshtml.cs | 10 + GanjooRazor/Pages/_GanjoorPagePartial.cshtml | 2 + GanjooRazor/wwwroot/css/audioplayer.css | 251 ++++++++++ GanjooRazor/wwwroot/js/audioplayer.js | 455 ++++++++++++++++++ GanjoorService.sln | 4 +- 8 files changed, 905 insertions(+), 2 deletions(-) create mode 100644 GanjooRazor/Pages/_AudioPlayerPartial.cshtml create mode 100644 GanjooRazor/Pages/_AudioPlayerPartial.cshtml.cs create mode 100644 GanjooRazor/wwwroot/css/audioplayer.css create mode 100644 GanjooRazor/wwwroot/js/audioplayer.js diff --git a/GanjooRazor/Pages/Index.cshtml b/GanjooRazor/Pages/Index.cshtml index 0fb01f13..379d0442 100644 --- a/GanjooRazor/Pages/Index.cshtml +++ b/GanjooRazor/Pages/Index.cshtml @@ -18,6 +18,7 @@ } + diff --git a/GanjooRazor/Pages/Index.cshtml.cs b/GanjooRazor/Pages/Index.cshtml.cs index c3e4fbef..bf156170 100644 --- a/GanjooRazor/Pages/Index.cshtml.cs +++ b/GanjooRazor/Pages/Index.cshtml.cs @@ -195,6 +195,14 @@ namespace GanjooRazor.Pages }; } + public _AudioPlayerPartialModel GetRecitationsModel(PublicRecitationViewModel[] recitations) + { + return new _AudioPlayerPartialModel() + { + Recitations = recitations + }; + } + public async Task OnPostReply(string replyCommentText, int refPoemId, int refCommentId) { return await OnPostComment(replyCommentText, refPoemId, refCommentId, -1); diff --git a/GanjooRazor/Pages/_AudioPlayerPartial.cshtml b/GanjooRazor/Pages/_AudioPlayerPartial.cshtml new file mode 100644 index 00000000..beb644f0 --- /dev/null +++ b/GanjooRazor/Pages/_AudioPlayerPartial.cshtml @@ -0,0 +1,176 @@ +@model GanjooRazor.Pages._AudioPlayerPartialModel +@{ +} +
+ + + +
+ +
+ Large toggle button +
+ + +
+
+
+
+ 00:00 / + 00:00 +
+
+ + +
+
+
+
+
+
+
+
+ +
+ + +
+ Previous track button + Next track button +
+ + +
+ + +
+ +
+
+ Small toggle button +
+
+ 1. +
+ +
+
+
+ Small toggle button +
+
+ 2. +
+ +
+
+
+ Small toggle button +
+
+ 3. +
+ +
+
+
+ Small toggle button +
+
+ 4. +
+ +
+
+
+ Small toggle button +
+
+ 5. +
+ +
+
+
+ Small toggle button +
+
+ 6. +
+ +
+
+
+ Small toggle button +
+
+ 7. +
+ +
+
+
+ Small toggle button +
+
+ 8. +
+ +
+
+
+ Small toggle button +
+
+ 9. +
+ +
+
+
+ Small toggle button +
+
+ 10. +
+ +
+ +
+ +
می‌خواهید شما بخوانید؟ اینجا را ببینید.
+
+ + diff --git a/GanjooRazor/Pages/_AudioPlayerPartial.cshtml.cs b/GanjooRazor/Pages/_AudioPlayerPartial.cshtml.cs new file mode 100644 index 00000000..e3f541da --- /dev/null +++ b/GanjooRazor/Pages/_AudioPlayerPartial.cshtml.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using RMuseum.Models.GanjoorAudio.ViewModels; + +namespace GanjooRazor.Pages +{ + public class _AudioPlayerPartialModel : PageModel + { + public PublicRecitationViewModel[] Recitations { get; set; } + } +} diff --git a/GanjooRazor/Pages/_GanjoorPagePartial.cshtml b/GanjooRazor/Pages/_GanjoorPagePartial.cshtml index 5d98d5e6..c22b3d3e 100644 --- a/GanjooRazor/Pages/_GanjoorPagePartial.cshtml +++ b/GanjooRazor/Pages/_GanjoorPagePartial.cshtml @@ -247,6 +247,8 @@ + + } } } diff --git a/GanjooRazor/wwwroot/css/audioplayer.css b/GanjooRazor/wwwroot/css/audioplayer.css new file mode 100644 index 00000000..e3f385ec --- /dev/null +++ b/GanjooRazor/wwwroot/css/audioplayer.css @@ -0,0 +1,251 @@ +.screen-reader-text { + /* Reusable, toolbox kind of class */ + position: absolute; + top: -9999px; + left: -9999px; +} + +.disabled { + color: #666; + cursor: default; +} + +.show { + display: inline-block !important; +} + + +.container { + margin: 0 auto; + width: 500px; +} +.container .player { + height: 60px; + margin: 0; + position: relative; + width: 400px; + /* Small devices (tablets, 768px and up) */ + /* Medium devices (desktops, 992px and up) */ + /* Large devices (large desktops, 1200px and up) */ + *zoom: 1; +} +@media (min-width: 768px) and (max-width: 991px) { + .container .player { + width: 470px; + } +} +@media (min-width: 992px) and (max-width: 1100px) { + .container .player { + width: 470px; + } +} +@media (min-width: 1200px) { + .container .player { + width: 470px; + } +} +.container .player .large-toggle-btn { + border: 1px solid #d9d9d9; + border-radius: 2px; + float: left; + font-size: 1.5em; + height: 50px; + margin: 0 10px 0 0; + overflow: hidden; + padding: 5px 0 0 0; + position: relative; + text-align: center; + vertical-align: bottom; + width: 54px; +} +.container .player .large-toggle-btn .large-play-btn { + cursor: pointer; + display: inline-block; + position: relative; + top: -14%; +} + +.container .player .large-toggle-btn .large-pause-btn { + cursor: pointer; + display: inline-block; + position: relative; + top: -13%; +} +.container .player .info-box { + bottom: 10px; + left: 65px; + position: absolute; + top: 15px; +} +.container .player .info-box .track-info-box { + float: left; + font-size: 12px; + margin: 0 0 6px 0; + visibility: hidden; + width: 400px; + *zoom: 1; +} +.container .player .info-box .track-info-box .track-title-text { + display: inline-block; +} +.container .player .info-box .track-info-box .audio-time { + display: inline-block; + padding: 0 0 0 5px; + width: 80px; +} +.container .player .info-box .track-info-box:before, .container .player .info-box .track-info-box:after { + content: " "; + display: table; +} +.container .player .info-box .track-info-box:after { + clear: both; + display: block; + font-size: 0; + height: 0; + visibility: hidden; +} +.container .player .progress-box { + float: left; + min-width: 270px; + position: relative; +} +.container .player .progress-box .progress-cell { + height: 12px; + position: relative; +} +.container .player .progress-box .progress-cell .progress { + background: #fff; + border: 1px solid #d9d9d9; + height: 8px; + position: relative; + width: auto; +} +.container .player .progress-box .progress-cell .progress .progress-buffer { + background: #337ab7; + height: 100%; + width: 0; +} +.container .player .progress-box .progress-cell .progress .progress-indicator { + background: #fff; + border: 1px solid #bebebe; + border-radius: 3px; + cursor: pointer; + height: 10px; + left: 0; + overflow: hidden; + position: absolute; + top: -2px; + width: 22px; +} +.container .player .controls-box { + bottom: 10px; + left: 350px; + position: absolute; +} +.container .player .controls-box .previous-track-btn { + cursor: pointer; + display: inline-block; +} +.container .player .controls-box .next-track-btn { + cursor: pointer; + display: inline-block; +} +.container .player:before, .container .player:after { + content: " "; + display: table; +} +.container .player:after { + clear: both; + display: block; + font-size: 0; + height: 0; + visibility: hidden; +} +.container .play-list { + display: block; + margin: 0 auto 20px auto; + width: 100%; +} +.container .play-list .play-list-row { + display: block; + margin: 10px 0; + width: 100%; + *zoom: 1; +} +.container .play-list .play-list-row .track-title .playlist-track { + color: #000; + text-decoration: none; +} +.container .play-list .play-list-row .track-title .playlist-track:hover { + text-decoration: underline; +} +.container .play-list .play-list-row .small-toggle-btn { + border: 1px solid #d9d9d9; + border-radius: 2px; + cursor: pointer; + display: inline-block; + height: 20px; + margin: 0 auto; + overflow: hidden; + position: relative; + text-align: center; + vertical-align: middle; + width: 20px; +} +.container .play-list .play-list-row .small-toggle-btn .small-play-btn { + display: inline-block; +} +.container .play-list .play-list-row .small-toggle-btn .small-pause-btn { + display: inline-block; +} +.container .play-list .play-list-row .track-number { + display: inline-block; +} +.container .play-list .play-list-row .track-title { + display: inline-block; +} +.container .play-list .play-list-row .track-title .playlist-track { + text-decoration: none; +} +.container .play-list .play-list-row .track-title .playlist-track:hover { + text-decoration: underline; +} +.container .play-list .play-list-row .track-title.active-track { + font-weight: bold; +} +.container .play-list .play-list-row:before, .container .play-list .play-list-row:after { + content: " "; + display: table; +} +.container .play-list .play-list-row:after { + clear: both; + display: block; + font-size: 0; + height: 0; + visibility: hidden; +} + +.container .player .large-toggle-btn .large-play-btn:before { + content: "play_arrow"; + font: 1.5em/1.75 "Material Icons"; +} +.container .player .large-toggle-btn .large-pause-btn:before { + content: "pause"; + font: 1.5em/1.75 "Material Icons"; +} +.container .player .controls-box .previous-track-btn:before { + content: "skip_previous"; + font: 1em "Material Icons"; +} +.container .player .controls-box .next-track-btn:before { + content: "skip_next"; + font: 1em "Material Icons"; +} +.container .play-list .play-list-row .small-toggle-btn .small-play-btn:before { + content: "play_arrow"; + font: 0.85em "Material Icons"; +} +.container .play-list .play-list-row .small-toggle-btn .small-pause-btn:before { + content: "pause"; + font: 0.85em "Material Icons"; +} \ No newline at end of file diff --git a/GanjooRazor/wwwroot/js/audioplayer.js b/GanjooRazor/wwwroot/js/audioplayer.js new file mode 100644 index 00000000..e2551c58 --- /dev/null +++ b/GanjooRazor/wwwroot/js/audioplayer.js @@ -0,0 +1,455 @@ +//original source: https://codepen.io/craigstroman/pen/aOyRYx +var audioPlayer = function() { + "use strict"; + + // Private variables + var _currentTrack = null; + var _elements = { + audio: document.getElementById("audio"), + playerButtons: { + largeToggleBtn: document.querySelector(".large-toggle-btn"), + nextTrackBtn: document.querySelector(".next-track-btn"), + previousTrackBtn: document.querySelector(".previous-track-btn"), + smallToggleBtn: document.getElementsByClassName("small-toggle-btn") + }, + progressBar: document.querySelector(".progress-box"), + playListRows: document.getElementsByClassName("play-list-row"), + trackInfoBox: document.querySelector(".track-info-box") + }; + var _playAHead = false; + var _progressCounter = 0; + var _progressBarIndicator = _elements.progressBar.children[0].children[0].children[1]; + var _trackLoaded = false; + + /** + * Determines the buffer progress. + * + * @param audio The audio element on the page. + **/ + var _bufferProgress = function(audio) { + var bufferedTime = (audio.buffered.end(0) * 100) / audio.duration; + var progressBuffer = _elements.progressBar.children[0].children[0].children[0]; + + progressBuffer.style.width = bufferedTime + "%"; + }; + + /** + * A utility function for getting the event cordinates based on browser type. + * + * @param e The JavaScript event. + **/ + var _getXY = function(e) { + var containerX = _elements.progressBar.offsetLeft; + var containerY = _elements.progressBar.offsetTop; + + var coords = { + x: null, + y: null + }; + + var isTouchSuopported = "ontouchstart" in window; + + if (isTouchSuopported) { //For touch devices + coords.x = e.clientX - containerX; + coords.y = e.clientY - containerY; + + return coords; + } else if (e.offsetX || e.offsetX === 0) { // For webkit browsers + coords.x = e.offsetX; + coords.y = e.offsetY; + + return coords; + } else if (e.layerX || e.layerX === 0) { // For Mozilla firefox + coords.x = e.layerX; + coords.y = e.layerY; + + return coords; + } + }; + + var _handleProgressIndicatorClick = function(e) { + var progressBar = document.querySelector(".progress-box"); + var xCoords = _getXY(e).x; + + return (xCoords - progressBar.offsetLeft) / progressBar.children[0].offsetWidth; + }; + + /** + * Initializes the html5 audio player and the playlist. + * + **/ + var initPlayer = function() { + + if (_currentTrack === 1 || _currentTrack === null) { + _elements.playerButtons.previousTrackBtn.disabled = true; + } + + //Adding event listeners to playlist clickable elements. + for (var i = 0; i < _elements.playListRows.length; i++) { + var smallToggleBtn = _elements.playerButtons.smallToggleBtn[i]; + var playListLink = _elements.playListRows[i].children[2].children[0]; + + //Playlist link clicked. + playListLink.addEventListener("click", function(e) { + e.preventDefault(); + var selectedTrack = parseInt(this.parentNode.parentNode.getAttribute("data-track-row")); + + if (selectedTrack !== _currentTrack) { + _resetPlayStatus(); + _currentTrack = null; + _trackLoaded = false; + } + + if (_trackLoaded === false) { + _currentTrack = parseInt(selectedTrack); + _setTrack(); + } else { + _playBack(this); + } + }, false); + + //Small toggle button clicked. + smallToggleBtn.addEventListener("click", function(e) { + e.preventDefault(); + var selectedTrack = parseInt(this.parentNode.getAttribute("data-track-row")); + + if (selectedTrack !== _currentTrack) { + _resetPlayStatus(); + _currentTrack = null; + _trackLoaded = false; + } + + if (_trackLoaded === false) { + _currentTrack = parseInt(selectedTrack); + _setTrack(); + } else { + _playBack(this); + } + + }, false); + } + + //Audio time has changed so update it. + _elements.audio.addEventListener("timeupdate", _trackTimeChanged, false); + + //Audio track has ended playing. + _elements.audio.addEventListener("ended", function(e) { + _trackHasEnded(); + }, false); + + //Audio error. + _elements.audio.addEventListener("error", function(e) { + switch (e.target.error.code) { + case e.target.error.MEDIA_ERR_ABORTED: + alert('You aborted the video playback.'); + break; + case e.target.error.MEDIA_ERR_NETWORK: + alert('A network error caused the audio download to fail.'); + break; + case e.target.error.MEDIA_ERR_DECODE: + alert('The audio playback was aborted due to a corruption problem or because the video used features your browser did not support.'); + break; + case e.target.error.MEDIA_ERR_SRC_NOT_SUPPORTED: + alert('The video audio not be loaded, either because the server or network failed or because the format is not supported.'); + break; + default: + alert('An unknown error occurred.'); + break; + } + trackLoaded = false; + _resetPlayStatus(); + }, false); + + //Large toggle button clicked. + _elements.playerButtons.largeToggleBtn.addEventListener("click", function(e) { + if (_trackLoaded === false) { + _currentTrack = parseInt(1); + _setTrack() + } else { + _playBack(); + } + }, false); + + //Next track button clicked. + _elements.playerButtons.nextTrackBtn.addEventListener("click", function(e) { + if (this.disabled !== true) { + _currentTrack++; + _trackLoaded = false; + _resetPlayStatus(); + _setTrack(); + } + }, false); + + //Previous track button clicked. + _elements.playerButtons.previousTrackBtn.addEventListener("click", function(e) { + if (this.disabled !== true) { + _currentTrack--; + _trackLoaded = false; + _resetPlayStatus(); + _setTrack(); + } + }, false); + + //User is moving progress indicator. + _progressBarIndicator.addEventListener("mousedown", _mouseDown, false); + + //User stops moving progress indicator. + window.addEventListener("mouseup", _mouseUp, false); + }; + + /** + * Handles the mousedown event by a user and determines if the mouse is being moved. + * + * @param e The event object. + **/ + var _mouseDown = function(e) { + window.addEventListener("mousemove", _moveProgressIndicator, true); + audio.removeEventListener("timeupdate", _trackTimeChanged, false); + + _playAHead = true; + }; + + /** + * Handles the mouseup event by a user. + * + * @param e The event object. + **/ + var _mouseUp = function(e) { + if (_playAHead === true) { + var duration = parseFloat(audio.duration); + var progressIndicatorClick = parseFloat(_handleProgressIndicatorClick(e)); + window.removeEventListener("mousemove", _moveProgressIndicator, true); + + audio.currentTime = duration * progressIndicatorClick; + audio.addEventListener("timeupdate", _trackTimeChanged, false); + _playAHead = false; + } + }; + + /** + * Moves the progress indicator to a new point in the audio. + * + * @param e The event object. + **/ + var _moveProgressIndicator = function(e) { + var newPosition = 0; + var progressBarOffsetLeft = _elements.progressBar.offsetLeft; + var progressBarWidth = 0; + var progressBarIndicator = _elements.progressBar.children[0].children[0].children[1]; + var progressBarIndicatorWidth = _progressBarIndicator.offsetWidth; + var xCoords = _getXY(e).x; + + progressBarWidth = _elements.progressBar.children[0].offsetWidth - progressBarIndicatorWidth; + newPosition = xCoords - progressBarOffsetLeft; + + if ((newPosition >= 1) && (newPosition <= progressBarWidth)) { + progressBarIndicator.style.left = newPosition + ".px"; + } + if (newPosition < 0) { + progressBarIndicator.style.left = "0"; + } + if (newPosition > progressBarWidth) { + progressBarIndicator.style.left = progressBarWidth + "px"; + } + }; + + /** + * Controls playback of the audio element. + * + **/ + var _playBack = function() { + if (_elements.audio.paused) { + _elements.audio.play(); + _updatePlayStatus(true); + } else { + _elements.audio.pause(); + _updatePlayStatus(false); + } + }; + + /** + * Sets the track if it hasn't already been loaded yet. + * + **/ + var _setTrack = function() { + var songURL = _elements.audio.children[_currentTrack - 1].src; + + _elements.audio.setAttribute("src", songURL); + _elements.audio.load(); + + _trackLoaded = true; + + _setTrackTitle(_currentTrack, _elements.playListRows); + + _setActiveItem(_currentTrack, _elements.playListRows); + + _elements.trackInfoBox.style.visibility = "visible"; + + _playBack(); + }; + + /** + * Sets the activly playing item within the playlist. + * + * @param currentTrack The current track number being played. + * @param playListRows The playlist object. + **/ + var _setActiveItem = function(currentTrack, playListRows) { + for (var i = 0; i < playListRows.length; i++) { + playListRows[i].children[2].className = "track-title"; + } + + playListRows[currentTrack - 1].children[2].className = "track-title active-track"; + }; + + /** + * Sets the text for the currently playing song. + * + * @param currentTrack The current track number being played. + * @param playListRows The playlist object. + **/ + var _setTrackTitle = function(currentTrack, playListRows) { + var trackTitleBox = document.querySelector(".player .info-box .track-info-box .track-title-text"); + var trackTitle = playListRows[currentTrack - 1].children[2].outerText; + + trackTitleBox.innerHTML = null; + + trackTitleBox.innerHTML = trackTitle; + + }; + + /** + * Plays the next track when a track has ended playing. + * + **/ + var _trackHasEnded = function() { + parseInt(_currentTrack); + _currentTrack = (_currentTrack === _elements.playListRows.length) ? 1 : _currentTrack + 1; + _trackLoaded = false; + + _resetPlayStatus(); + + _setTrack(); + }; + + /** + * Updates the time for the song being played. + * + **/ + var _trackTimeChanged = function() { + var currentTimeBox = document.querySelector(".player .info-box .track-info-box .audio-time .current-time"); + var currentTime = audio.currentTime; + var duration = audio.duration; + var durationBox = document.querySelector(".player .info-box .track-info-box .audio-time .duration"); + var trackCurrentTime = _trackTime(currentTime); + var trackDuration = _trackTime(duration); + + currentTimeBox.innerHTML = null; + currentTimeBox.innerHTML = trackCurrentTime; + + durationBox.innerHTML = null; + durationBox.innerHTML = trackDuration; + + _updateProgressIndicator(audio); + _bufferProgress(audio); + }; + + /** + * A utility function for converting a time in miliseconds to a readable time of minutes and seconds. + * + * @param seconds The time in seconds. + * + * @return time The time in minutes and/or seconds. + **/ + var _trackTime = function(seconds) { + var min = 0; + var sec = Math.floor(seconds); + var time = 0; + + min = Math.floor(sec / 60); + + min = min >= 10 ? min : '0' + min; + + sec = Math.floor(sec % 60); + + sec = sec >= 10 ? sec : '0' + sec; + + time = min + ':' + sec; + + return time; + }; + + /** + * Updates both the large and small toggle buttons accordingly. + * + * @param audioPlaying A booean value indicating if audio is playing or paused. + **/ + var _updatePlayStatus = function(audioPlaying) { + if (audioPlaying) { + _elements.playerButtons.largeToggleBtn.children[0].className = "large-pause-btn"; + + _elements.playerButtons.smallToggleBtn[_currentTrack - 1].children[0].className = "small-pause-btn"; + } else { + _elements.playerButtons.largeToggleBtn.children[0].className = "large-play-btn"; + + _elements.playerButtons.smallToggleBtn[_currentTrack - 1].children[0].className = "small-play-btn"; + } + + //Update next and previous buttons accordingly + if (_currentTrack === 1) { + _elements.playerButtons.previousTrackBtn.disabled = true; + _elements.playerButtons.previousTrackBtn.className = "previous-track-btn disabled"; + } else if (_currentTrack > 1 && _currentTrack !== _elements.playListRows.length) { + _elements.playerButtons.previousTrackBtn.disabled = false; + _elements.playerButtons.previousTrackBtn.className = "previous-track-btn"; + _elements.playerButtons.nextTrackBtn.disabled = false; + _elements.playerButtons.nextTrackBtn.className = "next-track-btn"; + } else if (_currentTrack === _elements.playListRows.length) { + _elements.playerButtons.nextTrackBtn.disabled = true; + _elements.playerButtons.nextTrackBtn.className = "next-track-btn disabled"; + } + }; + + /** + * Updates the location of the progress indicator according to how much time left in audio. + * + **/ + var _updateProgressIndicator = function() { + var currentTime = parseFloat(_elements.audio.currentTime); + var duration = parseFloat(_elements.audio.duration); + var indicatorLocation = 0; + var progressBarWidth = parseFloat(_elements.progressBar.offsetWidth); + var progressIndicatorWidth = parseFloat(_progressBarIndicator.offsetWidth); + var progressBarIndicatorWidth = progressBarWidth - progressIndicatorWidth; + + indicatorLocation = progressBarIndicatorWidth * (currentTime / duration); + + _progressBarIndicator.style.left = indicatorLocation + "px"; + }; + + /** + * Resets all toggle buttons to be play buttons. + * + **/ + var _resetPlayStatus = function() { + var smallToggleBtn = _elements.playerButtons.smallToggleBtn; + + _elements.playerButtons.largeToggleBtn.children[0].className = "large-play-btn"; + + for (var i = 0; i < smallToggleBtn.length; i++) { + if (smallToggleBtn[i].children[0].className === "small-pause-btn") { + smallToggleBtn[i].children[0].className = "small-play-btn"; + } + } + }; + + return { + initPlayer: initPlayer + }; +}; + +(function() { + var player = new audioPlayer(); + + player.initPlayer(); +})(); \ No newline at end of file diff --git a/GanjoorService.sln b/GanjoorService.sln index 3bdacee2..2d3fe143 100644 --- a/GanjoorService.sln +++ b/GanjoorService.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30011.22 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSecurityBackend", "RSecurityBackend\RSecurityBackend.csproj", "{1BF45DCD-40F4-4D6E-9134-D6B3DE39C56E}" EndProject