diff --git a/api/token.go b/api/token.go
index 60788f074..7f44f2c54 100644
--- a/api/token.go
+++ b/api/token.go
@@ -8,6 +8,7 @@ import (
 	"github.com/TUM-Dev/gocast/dao"
 	"github.com/TUM-Dev/gocast/model"
 	"github.com/TUM-Dev/gocast/tools"
+	"github.com/TUM-Dev/gocast/tools/tum"
 	"github.com/gin-gonic/gin"
 	uuid "github.com/satori/go.uuid"
 )
@@ -15,7 +16,8 @@ import (
 func configTokenRouter(r *gin.Engine, daoWrapper dao.DaoWrapper) {
 	routes := tokenRoutes{daoWrapper}
 	g := r.Group("/api/token")
-	g.Use(tools.Admin)
+	g.POST("/proxy/:token", routes.fetchStreamKey)
+	g.Use(tools.AtLeastLecturer)
 	g.POST("/create", routes.createToken)
 	g.DELETE("/:id", routes.deleteToken)
 }
@@ -26,7 +28,34 @@ type tokenRoutes struct {
 
 func (r tokenRoutes) deleteToken(c *gin.Context) {
 	id := c.Param("id")
-	err := r.TokenDao.DeleteToken(id)
+
+	foundContext, exists := c.Get("TUMLiveContext")
+	if !exists {
+		return
+	}
+	tumLiveContext := foundContext.(tools.TUMLiveContext)
+
+	token, err := r.TokenDao.GetTokenByID(id)
+	if err != nil {
+		logger.Error("can not get token", "err", err)
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusInternalServerError,
+			CustomMessage: "can not get token",
+			Err:           err,
+		})
+		return
+	}
+
+	// only the user who created the token or an admin can delete it
+	if token.UserID != tumLiveContext.User.ID && tumLiveContext.User.Role != model.AdminType {
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusForbidden,
+			CustomMessage: "not allowed to delete token",
+		})
+		return
+	}
+
+	err = r.TokenDao.DeleteToken(id)
 	if err != nil {
 		logger.Error("can not delete token", "err", err)
 		_ = c.Error(tools.RequestError{
@@ -58,13 +87,22 @@ func (r tokenRoutes) createToken(c *gin.Context) {
 		})
 		return
 	}
-	if req.Scope != model.TokenScopeAdmin {
+	if req.Scope == model.TokenScopeAdmin && tumLiveContext.User.Role != model.AdminType {
 		_ = c.Error(tools.RequestError{
 			Status:        http.StatusBadRequest,
 			CustomMessage: "not an admin",
 		})
 		return
 	}
+
+	if req.Scope != model.TokenScopeAdmin && req.Scope != model.TokenScopeLecturer {
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusBadRequest,
+			CustomMessage: "invalid scope",
+		})
+		return
+	}
+
 	tokenStr := uuid.NewV4().String()
 	expires := sql.NullTime{Valid: req.Expires != nil}
 	if req.Expires != nil {
@@ -90,3 +128,67 @@ func (r tokenRoutes) createToken(c *gin.Context) {
 		"token": tokenStr,
 	})
 }
+
+// This is used by the proxy to get the stream key of the next stream of the lecturer given a lecturer token
+//
+//	Proxy receives: rtmp://proxy.example.com/<lecturer-token>
+//				or: rtmp://proxy.example.com/<lecturer-token>?slug=ABC-123 <-- optional slug parameter in case the lecturer is streaming multiple courses simultaneously
+//
+//	Proxy returns:  rtmp://ingest.example.com/ABC-123?secret=610f609e4a2c43ac8a6d648177472b17
+func (r *tokenRoutes) fetchStreamKey(c *gin.Context) {
+	// Optional slug parameter to get the stream key of a specific course (in case the lecturer is streaming multiple courses simultaneously)
+	slug := c.Query("slug")
+	t := c.Param("token")
+
+	// Get user from token
+	token, err := r.TokenDao.GetToken(t)
+	if err != nil {
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusBadRequest,
+			CustomMessage: "invalid token",
+		})
+		return
+	}
+
+	// Only tokens of type lecturer are allowed to start streaming
+	if token.Scope != model.TokenScopeLecturer {
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusUnauthorized,
+			CustomMessage: "invalid scope",
+		})
+		return
+	}
+
+	// Get user and check if he has the right to start a stream
+	user, err := r.UsersDao.GetUserByID(c, token.UserID)
+	if err != nil {
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusInternalServerError,
+			CustomMessage: "could not get user",
+			Err:           err,
+		})
+		return
+
+	}
+	if user.Role != model.LecturerType && user.Role != model.AdminType {
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusUnauthorized,
+			CustomMessage: "user is not a lecturer or admin",
+		})
+		return
+	}
+
+	// Find current/next stream and course of which the user is a lecturer
+	year, term := tum.GetCurrentSemester()
+	streamKey, courseSlug, err := r.StreamsDao.GetSoonStartingStreamInfo(&user, slug, year, term)
+	if err != nil || streamKey == "" || courseSlug == "" {
+		_ = c.Error(tools.RequestError{
+			Status:        http.StatusNotFound,
+			CustomMessage: "no stream found",
+			Err:           err,
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{"url": "" + tools.Cfg.IngestBase + "/" + courseSlug + "?secret=" + streamKey + "/" + courseSlug})
+}
diff --git a/api/token_test.go b/api/token_test.go
index 01c6bb5f0..0a2088809 100644
--- a/api/token_test.go
+++ b/api/token_test.go
@@ -88,6 +88,7 @@ func TestToken(t *testing.T) {
 					wrapper := dao.DaoWrapper{
 						TokenDao: func() dao.TokenDao {
 							tokenMock := mock_dao.NewMockTokenDao(gomock.NewController(t))
+							tokenMock.EXPECT().GetTokenByID("1").Return(model.Token{}, nil).AnyTimes()
 							tokenMock.EXPECT().DeleteToken("1").Return(errors.New("")).AnyTimes()
 							return tokenMock
 						}(),
@@ -102,6 +103,7 @@ func TestToken(t *testing.T) {
 					wrapper := dao.DaoWrapper{
 						TokenDao: func() dao.TokenDao {
 							tokenMock := mock_dao.NewMockTokenDao(gomock.NewController(t))
+							tokenMock.EXPECT().GetTokenByID("1").Return(model.Token{}, nil).AnyTimes()
 							tokenMock.EXPECT().DeleteToken("1").Return(nil).AnyTimes()
 							return tokenMock
 						}(),
diff --git a/config.yaml b/config.yaml
index 7ded1393f..d89a8bdcf 100644
--- a/config.yaml
+++ b/config.yaml
@@ -103,3 +103,4 @@ meili:
   apiKey: MASTER_KEY
 vodURLTemplate: https://stream.lrz.de/vod/_definst_/mp4:tum/RBG/%s.mp4/playlist.m3u8
 canonicalURL: https://tum.live
+rtmpProxyURL: https://proxy.example.com
diff --git a/dao/courses.go b/dao/courses.go
index 7a169f0c9..a03557700 100644
--- a/dao/courses.go
+++ b/dao/courses.go
@@ -69,7 +69,7 @@ func (d coursesDao) CreateCourse(ctx context.Context, course *model.Course, keep
 
 func (d coursesDao) AddAdminToCourse(userID uint, courseID uint) error {
 	defer Cache.Clear()
-	return DB.Exec("insert into course_admins (user_id, course_id) values (?, ?)", userID, courseID).Error
+	return DB.Exec("insert into course_admins (user_id, course_id) values (?, ?) on duplicate key update user_id = user_id", userID, courseID).Error
 }
 
 // GetCurrentOrNextLectureForCourse Gets the next lecture for a course or the lecture that is currently live. Error otherwise.
diff --git a/dao/streams.go b/dao/streams.go
index fb838b531..92bfcaf75 100755
--- a/dao/streams.go
+++ b/dao/streams.go
@@ -3,12 +3,15 @@ package dao
 import (
 	"context"
 	"fmt"
+	"log/slog"
 	"strconv"
+	"strings"
 	"time"
 
 	"gorm.io/gorm/clause"
 
 	"github.com/TUM-Dev/gocast/model"
+	uuid "github.com/satori/go.uuid"
 	"gorm.io/gorm"
 )
 
@@ -32,6 +35,7 @@ type StreamsDao interface {
 	GetCurrentLiveNonHidden(ctx context.Context) (currentLive []model.Stream, err error)
 	GetLiveStreamsInLectureHall(lectureHallId uint) ([]model.Stream, error)
 	GetStreamsWithWatchState(courseID uint, userID uint) (streams []model.Stream, err error)
+	GetSoonStartingStreamInfo(user *model.User, slug string, year int, term string) (string, string, error)
 
 	SetLectureHall(streamIDs []uint, lectureHallID uint) error
 	UnsetLectureHall(streamIDs []uint) error
@@ -305,6 +309,107 @@ func (d streamsDao) GetStreamsWithWatchState(courseID uint, userID uint) (stream
 	return
 }
 
+// GetSoonStartingStreamInfo returns the stream key, course slug and course name of an upcoming stream.
+func (d streamsDao) GetSoonStartingStreamInfo(user *model.User, slug string, year int, term string) (string, string, error) {
+	var result struct {
+		CourseID  uint
+		StreamKey string
+		ID        string
+		Slug      string
+	}
+	now := time.Now()
+	query := DB.Table("streams").
+		Select("streams.course_id, streams.stream_key, streams.id, courses.slug").
+		Joins("JOIN course_admins ON course_admins.course_id = streams.course_id").
+		Joins("JOIN courses ON courses.id = course_admins.course_id").
+		Where("courses.slug != 'TESTCOURSE' AND streams.deleted_at IS NULL AND courses.deleted_at IS NULL AND course_admins.user_id = ? AND (streams.start <= ? AND streams.end >= ?)", user.ID, now.Add(15*time.Minute), now). // Streams starting in the next 15 minutes or currently running
+		Or("courses.slug != 'TESTCOURSE' AND streams.deleted_at IS NULL AND courses.deleted_at IS NULL AND course_admins.user_id = ? AND (streams.end >= ? AND streams.end <= ?)", user.ID, now.Add(-15*time.Minute), now).     // Streams that just finished in the last 15 minutes
+		Order("streams.start ASC")
+
+	if slug != "" {
+		query = query.Where("courses.slug = ?", slug)
+	}
+	if year != 0 {
+		query = query.Where("courses.year = ?", year)
+	}
+	if term != "" {
+		query = query.Where("courses.teaching_term = ?", term)
+	}
+
+	err := query.Limit(1).Scan(&result).Error
+	if err == gorm.ErrRecordNotFound || result.StreamKey == "" || result.ID == "" || result.Slug == "" {
+		stream, course, err := d.CreateOrGetTestStreamAndCourse(user)
+		if err != nil {
+			return "", "", err
+		}
+		return stream.StreamKey, fmt.Sprintf("%s-%d", course.Slug, stream.ID), nil
+	}
+	if err != nil {
+		logger.Error("Error getting soon starting stream: %v", slog.String("err", err.Error()))
+		return "", "", err
+	}
+
+	return result.StreamKey, fmt.Sprintf("%s-%s", result.Slug, result.ID), nil
+}
+
+// Helper method to fetch test stream and course for current user.
+func (d streamsDao) CreateOrGetTestStreamAndCourse(user *model.User) (model.Stream, model.Course, error) {
+	course, err := d.CreateOrGetTestCourse(user)
+	if err != nil {
+		return model.Stream{}, model.Course{}, err
+	}
+
+	var stream model.Stream
+	err = DB.FirstOrCreate(&stream, model.Stream{
+		CourseID:      course.ID,
+		Name:          "Test Stream",
+		Description:   "This is a test stream",
+		LectureHallID: 0,
+	}).Error
+	if err != nil {
+		return model.Stream{}, model.Course{}, err
+	}
+
+	stream.Start = time.Now().Add(5 * time.Minute)
+	stream.End = time.Now().Add(1 * time.Hour)
+	stream.LiveNow = true
+	stream.Recording = true
+	stream.LiveNowTimestamp = time.Now().Add(5 * time.Minute)
+	stream.Private = true
+	streamKey := uuid.NewV4().String()
+	stream.StreamKey = strings.ReplaceAll(streamKey, "-", "")
+	stream.LectureHallID = 1
+	err = DB.Save(&stream).Error
+	if err != nil {
+		return model.Stream{}, model.Course{}, err
+	}
+
+	return stream, course, err
+}
+
+// Helper method to fetch test course for current user.
+func (d streamsDao) CreateOrGetTestCourse(user *model.User) (model.Course, error) {
+	var course model.Course
+	err := DB.FirstOrCreate(&course, model.Course{
+		Name:         "(" + strconv.Itoa(int(user.ID)) + ") " + user.Name + "'s Test Course",
+		TeachingTerm: "Test",
+		Slug:         "TESTCOURSE",
+		Year:         1234,
+		Visibility:   "hidden",
+		VODEnabled:   false, // TODO: Change to VODEnabled: true for default testcourse if necessary
+	}).Error
+	if err != nil {
+		return model.Course{}, err
+	}
+
+	err = CoursesDao.AddAdminToCourse(NewDaoWrapper().CoursesDao, user.ID, course.ID)
+	if err != nil {
+		return model.Course{}, err
+	}
+
+	return course, nil
+}
+
 // SetLectureHall set lecture-halls of streamIds to lectureHallID
 func (d streamsDao) SetLectureHall(streamIDs []uint, lectureHallID uint) error {
 	return DB.Model(&model.Stream{}).Where("id IN ?", streamIDs).Update("lecture_hall_id", lectureHallID).Error
diff --git a/dao/token.go b/dao/token.go
index d91fc45b5..4cb1f0cb1 100644
--- a/dao/token.go
+++ b/dao/token.go
@@ -13,7 +13,8 @@ type TokenDao interface {
 	AddToken(token model.Token) error
 
 	GetToken(token string) (model.Token, error)
-	GetAllTokens() ([]AllTokensDto, error)
+	GetTokenByID(id string) (model.Token, error)
+	GetAllTokens(user *model.User) ([]AllTokensDto, error)
 
 	TokenUsed(token model.Token) error
 
@@ -40,11 +41,31 @@ func (d tokenDao) GetToken(token string) (model.Token, error) {
 	return t, err
 }
 
+func (d tokenDao) GetTokenByID(id string) (model.Token, error) {
+	var t model.Token
+	err := DB.Model(&t).Where("id = ?", id).First(&t).Error
+	return t, err
+}
+
 // GetAllTokens returns all tokens and the corresponding users name, email and lrz id
-func (d tokenDao) GetAllTokens() ([]AllTokensDto, error) {
+func (d tokenDao) GetAllTokens(user *model.User) ([]AllTokensDto, error) {
 	var tokens []AllTokensDto
-	err := DB.Raw("SELECT tokens.*, u.name as user_name, u.email as user_email, u.lrz_id as user_lrz_id FROM tokens JOIN users u ON u.id = tokens.user_id WHERE tokens.deleted_at IS null").Scan(&tokens).Error
-	return tokens, err
+
+	query := DB.Table("tokens").
+		Select("tokens.*, u.name as user_name, u.email as user_email, u.lrz_id as user_lrz_id").
+		Joins("JOIN users u ON u.id = tokens.user_id").
+		Where("tokens.deleted_at IS NULL")
+
+	if user.Role != model.AdminType {
+		query = query.Where("tokens.user_id = ?", user.ID)
+	}
+
+	err := query.Scan(&tokens).Error
+	if err != nil {
+		return nil, err
+	}
+
+	return tokens, nil
 }
 
 // TokenUsed is called when a token is used. It sets the last_used field to the current time.
diff --git a/go.work.sum b/go.work.sum
index 7443d3293..a86b78d80 100644
--- a/go.work.sum
+++ b/go.work.sum
@@ -329,6 +329,7 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF
 github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
 github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g=
+github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY=
 github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/djherbis/atime v1.1.0 h1:rgwVbP/5by8BvvjBNrbh64Qz33idKT3pSnMSJsxhi0g=
@@ -347,7 +348,6 @@ github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
 github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
 github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
 github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
-github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
@@ -377,7 +377,6 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
 github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
 github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM=
 github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9 h1:OF1IPgv+F4NmqmJ98KTjdN97Vs1JxDPB3vbmYzV2dpk=
 github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -594,6 +593,7 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
 github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
 github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
 github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8=
+github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
 go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
 go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
@@ -636,6 +636,8 @@ golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
 golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
@@ -644,6 +646,7 @@ golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
 golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
@@ -651,6 +654,9 @@ golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
 golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
 golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
 golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM=
 golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@@ -663,11 +669,16 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
 golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
 golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
 golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
 golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
@@ -677,6 +688,7 @@ golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
 golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
 golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es=
diff --git a/mock_dao/streams.go b/mock_dao/streams.go
index c3d7f3253..1a92479b7 100644
--- a/mock_dao/streams.go
+++ b/mock_dao/streams.go
@@ -243,6 +243,22 @@ func (mr *MockStreamsDaoMockRecorder) GetLiveStreamsInLectureHall(lectureHallId
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLiveStreamsInLectureHall", reflect.TypeOf((*MockStreamsDao)(nil).GetLiveStreamsInLectureHall), lectureHallId)
 }
 
+// GetSoonStartingStreamInfo mocks base method.
+func (m *MockStreamsDao) GetSoonStartingStreamInfo(user *model.User, slug string, year int, term string) (string, string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSoonStartingStreamInfo", user, slug, year, term)
+	ret0, _ := ret[0].(string)
+	ret1, _ := ret[1].(string)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
+}
+
+// GetSoonStartingStreamInfo indicates an expected call of GetSoonStartingStreamInfo.
+func (mr *MockStreamsDaoMockRecorder) GetSoonStartingStreamInfo(user, slug, year, term interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSoonStartingStreamInfo", reflect.TypeOf((*MockStreamsDao)(nil).GetSoonStartingStreamInfo), user, slug, year, term)
+}
+
 // GetStreamByID mocks base method.
 func (m *MockStreamsDao) GetStreamByID(ctx context.Context, id string) (model.Stream, error) {
 	m.ctrl.T.Helper()
diff --git a/mock_dao/token.go b/mock_dao/token.go
index a25fc1963..bd072f58c 100644
--- a/mock_dao/token.go
+++ b/mock_dao/token.go
@@ -64,18 +64,18 @@ func (mr *MockTokenDaoMockRecorder) DeleteToken(id interface{}) *gomock.Call {
 }
 
 // GetAllTokens mocks base method.
-func (m *MockTokenDao) GetAllTokens() ([]dao.AllTokensDto, error) {
+func (m *MockTokenDao) GetAllTokens(user *model.User) ([]dao.AllTokensDto, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "GetAllTokens")
+	ret := m.ctrl.Call(m, "GetAllTokens", user)
 	ret0, _ := ret[0].([]dao.AllTokensDto)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // GetAllTokens indicates an expected call of GetAllTokens.
-func (mr *MockTokenDaoMockRecorder) GetAllTokens() *gomock.Call {
+func (mr *MockTokenDaoMockRecorder) GetAllTokens(user interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllTokens", reflect.TypeOf((*MockTokenDao)(nil).GetAllTokens))
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllTokens", reflect.TypeOf((*MockTokenDao)(nil).GetAllTokens), user)
 }
 
 // GetToken mocks base method.
@@ -93,6 +93,21 @@ func (mr *MockTokenDaoMockRecorder) GetToken(token interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockTokenDao)(nil).GetToken), token)
 }
 
+// GetTokenByID mocks base method.
+func (m *MockTokenDao) GetTokenByID(id string) (model.Token, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTokenByID", id)
+	ret0, _ := ret[0].(model.Token)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTokenByID indicates an expected call of GetTokenByID.
+func (mr *MockTokenDaoMockRecorder) GetTokenByID(id interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTokenByID", reflect.TypeOf((*MockTokenDao)(nil).GetTokenByID), id)
+}
+
 // TokenUsed mocks base method.
 func (m *MockTokenDao) TokenUsed(token model.Token) error {
 	m.ctrl.T.Helper()
diff --git a/model/token.go b/model/token.go
index 2ae80c731..355aad62d 100644
--- a/model/token.go
+++ b/model/token.go
@@ -6,7 +6,10 @@ import (
 	"gorm.io/gorm"
 )
 
-const TokenScopeAdmin = "admin"
+const (
+	TokenScopeAdmin    = "admin"
+	TokenScopeLecturer = "lecturer"
+)
 
 // Token can be used to authenticate instead of a user account
 type Token struct {
diff --git a/tools/config.go b/tools/config.go
index 38911e29d..d439f32d4 100644
--- a/tools/config.go
+++ b/tools/config.go
@@ -174,6 +174,7 @@ type Config struct {
 	VodURLTemplate string `yaml:"vodURLTemplate"`
 	CanonicalURL   string `yaml:"canonicalURL"`
 	WikiURL        string `yaml:"wikiURL"`
+	RtmpProxyURL   string `yaml:"rtmpProxyURL"`
 }
 
 type MailConfig struct {
diff --git a/web/admin.go b/web/admin.go
index 95976d943..8ee7389b9 100644
--- a/web/admin.go
+++ b/web/admin.go
@@ -58,7 +58,7 @@ func (r mainRoutes) AdminPage(c *gin.Context) {
 			notifications = found
 		}
 	case "token":
-		tokens, err = r.TokenDao.GetAllTokens()
+		tokens, err = r.TokenDao.GetAllTokens(tumLiveContext.User)
 		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
 			logger.Error("couldn't query tokens", "err", err)
 			c.AbortWithStatus(http.StatusInternalServerError)
@@ -100,7 +100,7 @@ func (r mainRoutes) AdminPage(c *gin.Context) {
 			Semesters:           semesters,
 			CurY:                y,
 			CurT:                t,
-			Tokens:              tokens,
+			Tokens:              TokensData{Tokens: tokens, RtmpProxyURL: tools.Cfg.RtmpProxyURL, User: tumLiveContext.User},
 			InfoPages:           infopages,
 			ServerNotifications: serverNotifications,
 			Notifications:       notifications,
@@ -150,6 +150,12 @@ type WorkersData struct {
 	Token   string
 }
 
+type TokensData struct {
+	Tokens       []dao.AllTokensDto
+	RtmpProxyURL string
+	User         *model.User
+}
+
 func (r mainRoutes) LectureCutPage(c *gin.Context) {
 	foundContext, exists := c.Get("TUMLiveContext")
 	if !exists {
@@ -327,7 +333,7 @@ type AdminPageData struct {
 	CurT                string
 	EditCourseData      EditCourseData
 	ServerNotifications []model.ServerNotification
-	Tokens              []dao.AllTokensDto
+	Tokens              TokensData
 	InfoPages           []model.InfoPage
 	Notifications       []model.Notification
 }
diff --git a/web/template/admin/admin.gohtml b/web/template/admin/admin.gohtml
index 72a4ff16c..8019bf8f5 100755
--- a/web/template/admin/admin.gohtml
+++ b/web/template/admin/admin.gohtml
@@ -87,6 +87,37 @@
                     </ul>
                 </li>
             {{end}}
+            {{if eq $curUser.Role 2}}
+                <li class="mt-8"><h5
+                            class="mb-3 lg:mb-3 uppercase tracking-wide font-semibold text-sm lg:text-xs text-2">
+                        Streaming</h5>
+                    <ul>
+                        <li>
+                            <a class="px-3 py-2 transition-colors duration-200 {{if eq $page "users"}}text-1{{else}}text-5{{end}} relative block"
+                                href="/admin/token"><span
+                                        class="rounded-md absolute inset-0 bg-cyan-50 opacity-0"></span><span
+                                        class="relative">Token Management</span>
+                            </a>
+                        </li>
+                        <li>
+                            <a class="px-3 py-2 transition-colors duration-200 {{if eq $page "users"}}text-1{{else}}text-5{{end}} relative block"
+                                href="https://docs.live.rbg.tum.de/docs/usage/02-self-streaming/" target="_blank" rel="noopener noreferrer">
+                                <span class="rounded-md absolute inset-0 bg-cyan-50 opacity-0"></span>
+                                <span class="relative">Self-Streaming Guide</span>
+                                <svg class="inline-block ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 9l3 3m0 0l-3 3m3-3H3m18 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
+                            </a>
+                        </li>
+                        <li>
+                            <a class="px-3 py-2 transition-colors duration-200 {{if eq $page "users"}}text-1{{else}}text-5{{end}} relative block"
+                                href="https://docs.live.rbg.tum.de/" target="_blank" rel="noopener noreferrer">
+                                <span class="rounded-md absolute inset-0 bg-cyan-50 opacity-0"></span>
+                                <span class="relative">Need Help?</span>
+                                <svg class="inline-block ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 9l3 3m0 0l-3 3m3-3H3m18 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
+                            </a>
+                        </li>
+                    </ul>
+                </li>
+            {{end}}
             <li class="mt-8"><span
                         class="mb-3 lg:mb-3 uppercase tracking-wide font-semibold text-sm lg:text-xs text-2 flex">
                             <span class="grow">Courses</span><a href="/admin/create-course"><i
@@ -103,9 +134,16 @@
                             <a class="px-3 mb-3 lg:mb-3 uppercase tracking-wide font-semibold text-sm lg:text-xs transition-colors duration-200 text-5"
                                href="#" @click="expanded=!expanded">
                                 <i class="fas fa-chevron-right mr-1 transform" :class="expanded?'rotate-90':''"></i>
+                                <span class="rounded-md absolute bg-cyan-50 opacity-0"></span>
                                 <span
-                                        class="rounded-md absolute bg-cyan-50 opacity-0"></span><span
-                                        class="relative">{{$semester.Year}} - {{$semester.TeachingTerm}}</span></a>
+                                    class="relative">
+                                    {{if eq $semester.TeachingTerm "Test"}}
+                                        Test Course
+                                    {{else}}
+                                        {{$semester.Year}} - {{$semester.TeachingTerm}}
+                                    {{end}}
+                                </span>
+                            </a>
                             <ul id="{{printf "semesterCourses%d%s" $semester.Year $semester.TeachingTerm}}"
                                 class="semesterCourses pl-4"
                                 x-show="expanded">
@@ -196,6 +234,38 @@
                             </ul>
                         </li>
                     {{end}}
+       
+                    {{if eq $curUser.Role 2}}
+                        <li class="mt-8"><h5
+                                    class="mb-3 lg:mb-3 uppercase tracking-wide font-semibold text-sm lg:text-xs text-2">
+                                Streaming</h5>
+                            <ul>
+                                <li>
+                                    <a class="px-3 py-2 transition-colors duration-200 {{if eq $page "token"}}text-1{{else}}text-5{{end}} relative block"
+                                       href="/admin/token"><span
+                                                class="rounded-md absolute inset-0 bg-cyan-50 opacity-0"></span><span
+                                                class="relative">Token Management</span>
+                                    </a>
+                                </li>
+                                <li>
+                                    <a class="px-3 py-2 transition-colors duration-200 text-5 relative block"
+                                    href="https://docs.live.rbg.tum.de/docs/usage/02-self-streaming/" target="_blank" rel="noopener noreferrer">
+                                        <span class="rounded-md absolute inset-0 bg-cyan-50 opacity-0"></span>
+                                        <span class="relative">Self-Streaming Guide</span>
+                                        <svg class="inline-block ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 9l3 3m0 0l-3 3m3-3H3m18 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
+                                    </a>
+                                </li>
+                                <li>
+                                    <a class="px-3 py-2 transition-colors duration-200 text-5 relative block"
+                                    href="https://docs.live.rbg.tum.de/" target="_blank" rel="noopener noreferrer">
+                                        <span class="rounded-md absolute inset-0 bg-cyan-50 opacity-0"></span>
+                                        <span class="relative">Need Help?</span>
+                                        <svg class="inline-block ml-1 w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 9l3 3m0 0l-3 3m3-3H3m18 0a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
+                                    </a>
+                                </li>
+                            </ul>
+                        </li>
+                    {{end}}
                     <li class="mt-8"><span
                                 class="mb-3 lg:mb-3 uppercase tracking-wide font-semibold text-sm lg:text-xs text-2 flex">
                             <span class="grow">Courses</span><a title="Create Course" href="/admin/create-course"><i
@@ -214,18 +284,24 @@
                                         <i class="fas fa-chevron-right mr-1 transform"
                                            :class="expanded?'rotate-90':''"></i>
                                         <span
-                                                class="rounded-md absolute bg-cyan-50 opacity-0"></span><span
-                                                class="relative">{{$semester.Year}} - {{$semester.TeachingTerm}}</span></a>
-                                    <ul id="{{printf "semesterCourses%d%s" $semester.Year $semester.TeachingTerm}}"
-                                        class="semesterCourses pl-4"
-                                        x-show="expanded">
-                                        {{range $course := $courses}}{{if and (eq $course.Year $semester.Year) (eq $course.TeachingTerm $semester.TeachingTerm)}}
-                                            <li>
-                                                <a class="mx-3 my-2 transition-colors duration-200 {{if eq $page "course"}} {{if eq $indexData.TUMLiveContext.Course.Model.ID $course.Model.ID}}text-1{{else}} text-5 {{end}} {{else}}text-5{{end}} relative block"
-                                                   href="/admin/course/{{$course.Model.ID}}"><span
-                                                            class="rounded-md absolute bg-cyan-50 opacity-0"></span><span
-                                                            class="relative">{{$course.Name}}</span></a></li>
-                                        {{end}}
+                                            class="rounded-md absolute bg-cyan-50 opacity-0"></span><span 
+                                            class="relative">
+                                            {{if eq $semester.TeachingTerm "Test"}}
+                                                Test Courses
+                                            {{else}}
+                                                {{$semester.Year}} - {{$semester.TeachingTerm}}
+                                            {{end}}
+                                        </span></a>
+                                        <ul id="{{printf "semesterCourses%d%s" $semester.Year $semester.TeachingTerm}}"
+                                            class="semesterCourses pl-4"
+                                            x-show="expanded">
+                                            {{range $course := $courses}}{{if and (eq $course.Year $semester.Year) (eq $course.TeachingTerm $semester.TeachingTerm)}}
+                                                <li>
+                                                    <a class="mx-3 my-2 transition-colors duration-200 {{if eq $page "course"}} {{if eq $indexData.TUMLiveContext.Course.Model.ID $course.Model.ID}}text-1{{else}} text-5 {{end}} {{else}}text-5{{end}} relative block"
+                                                    href="/admin/course/{{$course.Model.ID}}"><span
+                                                                class="rounded-md absolute bg-cyan-50 opacity-0"></span><span
+                                                                class="relative">{{$course.Name}}</span></a></li>
+                                            {{end}}
                                         {{end}}
                                     </ul>
                                 </li>
@@ -261,8 +337,8 @@
                     {{template "create-course" .IndexData.VersionTag}}
                 {{else if and (eq $curUser.Role 1) (eq .Page "courseImport")}}
                     {{template "course-import" .}}
-                {{else if and (eq $curUser.Role 1) (eq .Page "token")}}
-                    {{template "token" .Tokens}}
+                {{else if and (or (eq $curUser.Role 1) (eq $curUser.Role 2)) (eq .Page "token")}}
+                    {{template "token" dict "Tokens" .Tokens "Role" $curUser.Role}}
                 {{else if and (eq $curUser.Role 1) (eq .Page "info-pages")}}
                     {{template "info-pages" .InfoPages}}
                 {{else if and (eq $curUser.Role 1) (eq .Page "notifications")}}
diff --git a/web/template/admin/admin_tabs/token.gohtml b/web/template/admin/admin_tabs/token.gohtml
index 8ce4d64c8..9104d724d 100644
--- a/web/template/admin/admin_tabs/token.gohtml
+++ b/web/template/admin/admin_tabs/token.gohtml
@@ -2,26 +2,26 @@
 <link rel="stylesheet" href="/static/node_modules/flatpickr/dist/flatpickr.min.css">
 <script src="/static/node_modules/flatpickr/dist/flatpickr.min.js"></script>
 
-<form class="form-container" x-data="{expires: '', scope: 'admin', generatedToken:null}"
+<form class="form-container" x-data="{expires: '', scope: 'lecturer', generatedToken:null}"
       @submit.prevent="admin.createToken(expires, scope).then(r=>r.json()).then(r => generatedToken=r.token)">
 
     <h1 class="form-container-title">Token Management</h1>
     <div class="form-container-body grid grid-cols-2 gap-3">
         <table class="table-auto w-full col-span-full">
             <thead>
-            <tr class="text-2 uppercase text-left">
-                <th>User</th>
+            <tr class="text-2 uppercase text-center">
+                <th class="px-4 text-left">User</th>
                 <th>Scope</th>
                 <th>Last Used</th>
                 <th>Expires</th>
                 <th>Actions</th>
             </tr>
             </thead>
-            <tbody class="text-3">
-            {{range .}}
-                {{- /*gotype: github.com/TUM-Dev/gocast/dao.AllTokensDto*/ -}}
+            <tbody class="text-3 text-center">
+            {{range .Tokens.Tokens}}
+                {{- /*gotype: github.com/TUM-Dev/gocast/web.TokensData*/ -}}
                 <tr x-data="{id: {{.Token.Model.ID}}, show:true}" x-show="show">
-                    <td class="p-4">{{if .UserMail}}{{.UserMail}}{{else}}{{.UserName}} {{.UserLrzID}}{{end}}</td>
+                    <td class="p-4 text-left">{{if .UserMail}}{{.UserMail}}{{else}}{{.UserName}} {{.UserLrzID}}{{end}}</td>
                     <td>{{.Scope}}</td>
                     <td>{{if .Token.LastUse.Valid}}{{.Token.LastUse.Time.Format "02 Jan 06 15:04:05"}}{{else}}never
                         used{{end}}</td>
@@ -40,17 +40,110 @@
                    x-init="flatpickr($el)">
         </label>
         <select x-model="scope" class="tl-select">
-            <option value="admin" class="text-4">
+            <option value="lecturer" class="text-4">
+                Scope: lecturer
+            </option>
+            <option value="admin" class="text-4" x-show="role == 1" x-init="role = {{.Role}}">
                 Scope: admin
             </option>
         </select>
-        <p x-show="generatedToken !== null" class="text-2">
-            This is your token. Write it down and keep it safe:
-            <span class="font-bold" x-text="generatedToken"></span>
-        </p>
         <button type="submit" class="btn primary col-span-full">
             <i class="fas fa-plus mr-1"></i>Create
         </button>
     </div>
+    <div x-show="generatedToken !== null" class="rounded-lg p-6 mb-4 text-1">
+        <div class="flex items-center justify-between">
+        <h3 class="text-lg font-semibold mb-2">Your Generated Token</h3>
+        <code @click="global.copyToClipboard(generatedToken)" class="block p-2 rounded-md font-mono text-sm overflow-x-auto bg-gray-200 dark:bg-secondary cursor-pointer"><span x-text="generatedToken"></span></code>
+        </div>
+        <p class="mb-4">This is your generated token. Please <span @click="global.copyToClipboard(generatedToken)" class="btn primary hover:bg-blue-700 text-white font-bold py-1 px-4 rounded cursor-pointer" x-init="generatedToken=generatedToken">Copy</span> it and store it securely. It will not be shown again.<br>To use this token for self-streaming, follow the instructions below:</p>
+        <p class="border-t border-gray-100 dark:border-gray-500 my-4" x-show="scope === 'lecturer'"></span>
+        <div class="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg" x-show="scope === 'lecturer'">
+                <div class="mb-4">
+                    <div x-data="{ tab: 'OBS' }">
+                        <div class="flex">
+                            <button type="button" @click="tab = 'OBS'" :class="tab === 'OBS' ? 'primary text-bold' : 'bg-gray-200 dark:bg-secondary'" class="px-4 py-2 rounded-t-lg">OBS</button>
+                            <button type="button" @click="tab = 'Zoom'" :class="tab === 'Zoom' ? 'primary text-bold' : 'bg-gray-200 dark:bg-secondary'" class="px-4 py-2 rounded-t-lg">Zoom</button>
+                            <button type="button" @click="tab = 'Teams'" :class="tab === 'Teams' ? 'primary text-bold' : 'bg-gray-200 dark:bg-secondary'" class="px-4 py-2 rounded-t-lg">Teams</button>
+                        </div>
+                        <div x-show="tab === 'OBS'" class="p-4 border-t border-gray-200 dark:border-gray-700">
+                            <ol class="list-decimal list-inside">
+                                <li class="mb-2">Open OBS.</li>
+                                <li class="mb-2">Go to <strong>File</strong> > <strong>Settings</strong> > <strong>Stream</strong>.</li>
+                                <li class="mb-2">Select the <strong>Custom</strong> service and enter the <strong>Server</strong> and <strong>Stream Key</strong> from below.</li>
+                                <li class="mb-2">Click <strong>Start Streaming</strong> to go live.</li>
+                            </ol>
+                             <p class="border-t border-gray-200 dark:border-gray-500 my-4"></p>
+                            <p class="mb-2">
+                                <strong>Server:</strong> 
+                                <code @click="global.copyToClipboard(`{{.Tokens.RtmpProxyURL}}`)" class="p-1 pt-2 rounded-md font-mono text-sm overflow-x-auto bg-gray-200 dark:bg-secondary cursor-pointer">
+                                    {{.Tokens.RtmpProxyURL}}
+                                </code>
+                            </p>
+                            <p>
+                                <strong>Stream Key:</strong> 
+                                <code @click="global.copyToClipboard(generatedToken)" class="p-1 pt-2 rounded-md font-mono text-sm overflow-x-auto bg-gray-200 dark:bg-secondary cursor-pointer">
+                                    <span x-text="generatedToken"></span>
+                                </code> 
+                            </p>
+                             <p class="border-t border-gray-200 dark:border-gray-500 mt-4"></p>
+                        </div>
+                        <div x-show="tab === 'Zoom'" class="p-4 border-t border-gray-200 dark:border-gray-700">
+                            <ol class="list-decimal list-inside">
+                                <li class="mb-2">Sign in to the Zoom web portal.</li>
+                                <li class="mb-2">Click <strong>Meetings</strong>.</li>
+                                <li class="mb-2">Click <strong>Schedule a Meeting</strong> and enter the required information to schedule a meeting.</li>
+                                <li class="mb-2">Click <strong>Save</strong> to display a set of tabs with advanced options.</li>
+                                <li class="mb-2">Click the <strong>Live Streaming</strong> tab, then click <strong>Configure Custom Streaming Service</strong>.</li>
+                                <li class="mb-2">Follow the instructions located in the green box, which were provided by your administrator. Contact your administrator if the instructions do not include sufficient information, or enable <strong>Configure live stream during the meeting</strong> to enter the details live.</li>
+                                <li class="mb-2">Click <strong>Save</strong> to save your livestreaming settings. The host will be able to livestream this meeting without needing to add these settings after the meeting begins.</li>
+                            </ol>
+                             <p class="border-t border-gray-200 dark:border-gray-500 my-4"></p>
+                            <p class="mb-2">
+                                <strong>Stream URL:</strong> 
+                                <code @click="global.copyToClipboard(`{{.Tokens.RtmpProxyURL}}`)" class="p-1 pt-2 rounded-md font-mono text-sm overflow-x-auto bg-gray-200 dark:bg-secondary cursor-pointer">
+                                    {{.Tokens.RtmpProxyURL}}
+                                </code>
+                            </p>
+                            <p>
+                                <strong>Stream Key:</strong> 
+                                <code @click="global.copyToClipboard(generatedToken)" class="p-1 pt-2 rounded-md font-mono text-sm overflow-x-auto bg-gray-200 dark:bg-secondary cursor-pointer">
+                                    <span x-text="generatedToken"></span>
+                                </code> 
+                            </p>
+                             <p class="border-t border-gray-200 dark:border-gray-500 mt-4"></p>
+                        </div>
+                        <div x-show="tab === 'Teams'" class="p-4 border-t border-gray-200 dark:border-gray-700">
+                            <ol class="list-decimal list-inside">
+                                <li class="mb-2">Open Microsoft Teams and oin the meeting or webinar you wish to live stream.</li>
+                                <li class="mb-2">Add the <strong>Custom Streaming</strong> app to the meeting.</li>
+                                <li class="mb-2">Click <strong>Add</strong> and <strong>Save</strong>.</li>
+                                <li class="mb-2">In the right-hand panel that opens, paste the <strong>Stream URL</strong> and <strong>Stream Key</strong> from below.</li>
+                                <li class="mb-2">Click <strong>Start streaming</strong> in the lower right, then select <strong>Allow</strong> in the dialog box when it appears.</li>
+                                <li class="mb-2">You’re now live streaming! Share your screen and/or use your cameras and microphones to run your event as you would any normal Microsoft Teams meeting.</li>
+                                <li class="mb-2">When you’re finished with the event, you can stop streaming via Teams and YouTube.</li>
+                            </ol>
+                             <p class="border-t border-gray-200 dark:border-gray-500 my-4"></p>
+                            <p class="mb-2">
+                                <strong>Stream URL:</strong> 
+                                <code @click="global.copyToClipboard(`{{.Tokens.RtmpProxyURL}}`)" class="p-1 pt-2 rounded-md font-mono text-sm overflow-x-auto bg-gray-200 dark:bg-secondary cursor-pointer">
+                                    {{.Tokens.RtmpProxyURL}}
+                                </code>
+                            </p>
+                            <p>
+                                <strong>Stream Key:</strong> 
+                                <code @click="global.copyToClipboard(generatedToken)" class="p-1 pt-2 rounded-md font-mono text-sm overflow-x-auto bg-gray-200 dark:bg-secondary cursor-pointer">
+                                    <span x-text="generatedToken"></span>
+                                </code> 
+                            </p>
+                             <p class="border-t border-gray-200 dark:border-gray-500 mt-4"></p>
+                        </div>
+                    </div>
+                    <p class="text-sm text-5 px-4">You can start streaming from 15 minutes before the lecture starts and up to 15 minutes after the lecture ends - TUMLive automatically finds the lecture you want to stream.</p>
+                    <p class="text-sm text-5 px-4">To test your setup, you can start streaming while not in a lecture and a private test stream will be created.</p>
+                </div>
+          <p class="mt-4 italic">For more information, please refer to the <a href="https://docs.live.rbg.tum.de/docs/usage/02-self-streaming/" target="_blank" rel="noopener noreferrer" class="text-blue-500 dark:text-blue-400 hover:text-blue-700 underline">self-streaming guide</a>.</p>
+        </div>
+    </div>
 </form>
 {{end}}